Skip to content

Commit

Permalink
Updates to Schnorr, ECDH, and Context
Browse files Browse the repository at this point in the history
  • Loading branch information
csjones committed Sep 17, 2023
1 parent f290fcd commit 4d096e2
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 113 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ Long-term goals are:
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:

```swift
.package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", from: "0.12.0"),
.package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.13.0"),
```

Include `secp256k1` as a dependency for your executable target:

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

Expand Down Expand Up @@ -63,8 +63,8 @@ print(try! signature.derRepresentation.base64EncodedString())
## Schnorr

```swift
// Disable BIP340 to enable Schnorr signatures of variable length messages
let privateKey = try! secp256k1.Schnorr.PrivateKey(strict: false)
// 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
Expand All @@ -91,11 +91,14 @@ let tweakedPublicKeyKey = try! privateKey.publicKey.add(tweak)
let privateKey = try! secp256k1.KeyAgreement.PrivateKey()
let publicKey = try! secp256k1.KeyAgreement.PrivateKey().publicKey

// Create a shared secret with a private key from only a public key
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: 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
## Silent Payments Scheme

```swift
let privateSign1 = try! secp256k1.Signing.PrivateKey()
Expand Down
1 change: 1 addition & 0 deletions Sources/secp256k1/Context.swift
69 changes: 69 additions & 0 deletions Sources/zkp/Context.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// Context.swift
// GigaBitcoin/secp256k1.swift
//
// Copyright (c) 2021 GigaBitcoin LLC
// Distributed under the MIT software license
//
// See the accompanying file LICENSE for information
//

/// A public extension that provides additional functionality to the `secp256k1` structure.
///
/// This extension includes a nested structure, `Context`, which represents the context for `secp256k1` operations.
/// The primary purpose of context objects is to store randomization data for enhanced protection against side-channel
/// leakage. This protection is only effective if the context is randomized after its creation.
///
/// A constructed context can safely be used from multiple threads simultaneously, but API calls that take a non-const
/// pointer to a context need exclusive access to it. In particular this is the case for `secp256k1_context_destroy`,
/// `secp256k1_context_preallocated_destroy`, and `secp256k1_context_randomize`.
public extension secp256k1 {
/// A structure that represents the context flags for `secp256k1` operations.
///
/// This structure conforms to the `OptionSet` protocol, allowing you to combine different context flags.
/// It includes a static property, `none`, which represents a `Context` with no flags.
///
/// The `Context` structure is used to create and manage the context for `secp256k1` operations.
/// It is used in the creation of the `secp256k1` context and also in determining the size of the preallocated
/// memory for the context.
struct Context: OptionSet {
/// The raw representation of `secp256k1.Context`
public static let rawRepresentation = try! secp256k1.Context.create()

/// The raw value of the context flags.
public let rawValue: UInt32

/// Creates a new `Context` instance with the specified raw value.
public init(rawValue: UInt32) { self.rawValue = rawValue }

/// Initializes a new Context with the specified raw value.
/// - Parameter rawValue: The Int32 raw value for the context flags.
init(rawValue: Int32) { self.rawValue = UInt32(rawValue) }

/// No context flag.
///
/// This static property represents a `Context` with no flags. It can be used when creating a `secp256k1`
/// context with no flags.
public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE)

/// Creates a new `secp256k1` context with the specified flags.
///
/// - Parameter context: The context flags to create a new `secp256k1` context.
/// - Throws: An error of type `secp256k1Error.underlyingCryptoError` if the context creation or randomization
/// fails.
/// - Returns: An opaque pointer to the created context.
///
/// This static method creates a new `secp256k1` context with the specified flags. The flags are represented by
/// the `Context` structure. The method throws an error if the context creation or randomization fails. If the
/// context creation is successful, the method returns an opaque pointer to the created context.
public static func create(_ context: Context = .none) throws -> OpaquePointer {
var randomBytes = SecureBytes(count: secp256k1.ByteLength.privateKey).bytes
guard let context = secp256k1_context_create(context.rawValue),
secp256k1_context_randomize(context, &randomBytes).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return context
}
}
}
3 changes: 3 additions & 0 deletions Sources/zkp/DH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ protocol DiffieHellmanKeyAgreement {
public struct SharedSecret: ContiguousBytes {
var ss: SecureBytes

// An enum that represents the format of the shared secret
let format: secp256k1.Format

public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
try ss.withUnsafeBytes(body)
}
Expand Down
37 changes: 29 additions & 8 deletions Sources/zkp/ECDH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,31 +140,52 @@ extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement {
/// - Returns: Returns a shared secret.
/// - Throws: An error occurred while computing the shared secret.
func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
try sharedSecretFromKeyAgreement(with: publicKeyShare, handler: nil)
try sharedSecretFromKeyAgreement(with: publicKeyShare, format: .compressed)
}

/// Performs a key agreement with the provided public key share.
///
/// - Parameters:
/// - publicKeyShare: The public key to perform the ECDH with.
/// - handler: Closure for customizing a hash function; Defaults to nil.
/// - data: Additional data for the hash function; Defaults to nil.
/// - format: An enum that represents the format of the shared secret.
/// - Returns: Returns a shared secret.
/// - Throws: An error occurred while computing the shared secret.
public func sharedSecretFromKeyAgreement(
with publicKeyShare: secp256k1.KeyAgreement.PublicKey,
handler: HashFunctionType? = nil,
data: UnsafeMutableRawPointer? = nil
format: secp256k1.Format = .compressed
) throws -> SharedSecret {
let context = secp256k1.Context.rawRepresentation
var publicKey = secp256k1_pubkey()
var sharedSecret = [UInt8](repeating: 0, count: 32)
var sharedSecret = [UInt8](repeating: 0, count: format.length)
var data = [UInt8](repeating: format == .compressed ? 1 : 0, count: 1)

guard secp256k1_ec_pubkey_parse(context, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue,
secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, handler, data).boolValue else {
secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, hashClosure(), &data).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return SharedSecret(ss: SecureBytes(bytes: sharedSecret))
return SharedSecret(ss: SecureBytes(bytes: sharedSecret), format: format)
}

/// Creates a closure which handles creating either a compressed or uncompressed shared secret
///
/// - Returns: Closure to override the libsecp256k1 hashing function
func hashClosure() -> HashFunctionType {
{ output, x32, y32, data in
guard let output, let x32, let y32, let compressed = data?.load(as: Bool.self) else { return 0 }

let lastByte = y32.advanced(by: secp256k1.ByteLength.dimension - 1).pointee
let version: UInt8 = compressed ? (lastByte & 0x01) | 0x02 : 0x04

output.update(repeating: version, count: 1)
output.advanced(by: 1).update(from: x32, count: secp256k1.ByteLength.dimension)

if compressed == false {
output.advanced(by: secp256k1.ByteLength.dimension + 1)
.update(from: y32, count: secp256k1.ByteLength.dimension)
}

return 1
}
}
}
13 changes: 6 additions & 7 deletions Sources/zkp/ECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import Foundation

typealias NISTECDSASignature = DataSignature & DERSignature
typealias NISTECDSASignature = DERSignature & DataSignature

protocol DataSignature {
init<D: DataProtocol>(dataRepresentation: D) throws
Expand Down Expand Up @@ -41,7 +41,7 @@ public extension secp256k1.Signing {
/// - dataRepresentation: A data representation of the key as a collection of contiguous bytes.
/// - Throws: If there is a failure with the dataRepresentation count
public init<D: DataProtocol>(dataRepresentation: D) throws {
guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount else {
guard dataRepresentation.count == secp256k1.ByteLength.signature else {
throw secp256k1Error.incorrectParameterSize
}

Expand All @@ -52,8 +52,8 @@ public extension secp256k1.Signing {
/// - Parameters:
/// - dataRepresentation: A data representation of the key as a collection of contiguous bytes.
/// - Throws: If there is a failure with the dataRepresentation count
internal init(_ dataRepresentation: Data) throws {
guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount else {
init(_ dataRepresentation: Data) throws {
guard dataRepresentation.count == secp256k1.ByteLength.signature else {
throw secp256k1Error.incorrectParameterSize
}

Expand Down Expand Up @@ -112,9 +112,8 @@ public extension secp256k1.Signing {
public var compactRepresentation: Data {
get throws {
let context = secp256k1.Context.rawRepresentation
let compactSignatureLength = 64
var signature = secp256k1_ecdsa_signature()
var compactSignature = [UInt8](repeating: 0, count: compactSignatureLength)
var compactSignature = [UInt8](repeating: 0, count: secp256k1.ByteLength.signature)

dataRepresentation.copyToUnsafeMutableBytes(of: &signature.data)

Expand All @@ -126,7 +125,7 @@ public extension secp256k1.Signing {
throw secp256k1Error.underlyingCryptoError
}

return Data(bytes: &compactSignature, count: compactSignatureLength)
return Data(bytes: &compactSignature, count: secp256k1.ByteLength.signature)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/zkp/HashDigest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public struct HashDigest: Digest {

/// The byte count of the hash digest.
public static var byteCount: Int {
get { 32 }
get { SHA256.digestByteCount }
set { fatalError("Cannot set SHA256.byteCount") }
}

Expand Down
11 changes: 5 additions & 6 deletions Sources/zkp/Recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,9 @@ public extension secp256k1.Recovery {
public var compactRepresentation: ECDSACompactSignature {
get throws {
let context = secp256k1.Context.rawRepresentation
let compactSignatureLength = 64
var recoveryId = Int32()
var recoverableSignature = secp256k1_ecdsa_recoverable_signature()
var compactSignature = [UInt8](repeating: 0, count: compactSignatureLength)
var compactSignature = [UInt8](repeating: 0, count: secp256k1.ByteLength.signature)

dataRepresentation.copyToUnsafeMutableBytes(of: &recoverableSignature.data)

Expand All @@ -145,7 +144,7 @@ public extension secp256k1.Recovery {
}

return secp256k1.Recovery.ECDSACompactSignature(
signature: Data(bytes: &compactSignature, count: compactSignatureLength),
signature: Data(bytes: &compactSignature, count: secp256k1.ByteLength.signature),
recoveryId: recoveryId
)
}
Expand Down Expand Up @@ -177,7 +176,7 @@ public extension secp256k1.Recovery {
/// - dataRepresentation: A data representation of the key as a collection of contiguous bytes.
/// - Throws: If there is a failure with the dataRepresentation count
public init<D: DataProtocol>(dataRepresentation: D) throws {
guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount + 1 else {
guard dataRepresentation.count == secp256k1.ByteLength.signature + 1 else {
throw secp256k1Error.incorrectParameterSize
}

Expand All @@ -188,8 +187,8 @@ public extension secp256k1.Recovery {
/// - Parameters:
/// - dataRepresentation: A data representation of the key as a collection of contiguous bytes.
/// - Throws: If there is a failure with the dataRepresentation count
internal init(_ dataRepresentation: Data) throws {
guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount + 1 else {
init(_ dataRepresentation: Data) throws {
guard dataRepresentation.count == secp256k1.ByteLength.signature + 1 else {
throw secp256k1Error.incorrectParameterSize
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/zkp/SHA256.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ import Foundation

/// The SHA256 hashing algorithm.
public enum SHA256 {
/// The number of bytes in a SHA256 digest.
@inlinable
static var digestByteCount: Int {
32
}

/// Computes a digest of the data.
/// - Parameter data: The data to be hashed.
/// - Returns: The computed digest.
public static func hash<D: DataProtocol>(data: D) -> SHA256Digest {
let stringData = Array(data)
var output = [UInt8](repeating: 0, count: 32)
var output = [UInt8](repeating: 0, count: Self.digestByteCount)

secp256k1_swift_sha256(&output, stringData, stringData.count)

Expand All @@ -34,7 +40,7 @@ public enum SHA256 {
let context = secp256k1.Context.rawRepresentation
let tagBytes = Array(tag)
let messageBytes = Array(data)
var output = [UInt8](repeating: 0, count: 32)
var output = [UInt8](repeating: 0, count: Self.digestByteCount)

guard secp256k1_tagged_sha256(
context,
Expand Down
2 changes: 1 addition & 1 deletion Sources/zkp/SafeCompare.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import Foundation

/// A constant-time comparison function for any two collections of bytes.
internal func safeCompare<LHS: ContiguousBytes, RHS: ContiguousBytes>(_ lhs: LHS, _ rhs: RHS) -> Bool {
func safeCompare<LHS: ContiguousBytes, RHS: ContiguousBytes>(_ lhs: LHS, _ rhs: RHS) -> Bool {
let lBytes = lhs.bytes
let rBytes = rhs.bytes

Expand Down
Loading

0 comments on commit 4d096e2

Please sign in to comment.