Skip to content

Commit

Permalink
Public Key and ECDH revisions (#218)
Browse files Browse the repository at this point in the history
  • Loading branch information
csjones authored Jul 23, 2022
1 parent b5fa560 commit b803887
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 38 deletions.
4 changes: 2 additions & 2 deletions Sources/implementation/Asymmetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ public extension secp256k1 {
/// Generates a secp256k1 public key from a raw representation.
/// - Parameter data: A raw representation of the key.
/// - Throws: An error is thrown when the raw representation does not create a public key.
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format) throws {
self.baseKey = try PublicKeyImplementation(rawRepresentation: data, format: format)
}
}

Expand Down
60 changes: 54 additions & 6 deletions Sources/implementation/ECDH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,55 @@ public extension secp256k1 {
enum KeyAgreement {
public struct PublicKey /*: NISTECPublicKey */ {
let baseKey: PublicKeyImplementation

/// Creates a secp256k1 public key for key agreement from a collection of bytes.
/// - Parameters:
/// - data: A raw representation of the public key as a collection of contiguous bytes.
/// - xonly: A raw representation of the xonly key as a collection of contiguous bytes.
/// - format: the format of the public key object
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws {
self.baseKey = try PublicKeyImplementation(rawRepresentation: data, format: format)
}

/// Initializes a secp256k1 public key for key agreement.
/// - Parameter baseKey: generated secp256k1 public key.
init(baseKey: PublicKeyImplementation) {
self.baseKey = baseKey
}

/// The associated x-only public key for verifying Schnorr signatures.
///
/// - Returns: The associated x-only public key
public var xonly: secp256k1.KeyAgreement.XonlyKey {
XonlyKey(baseKey: baseKey.xonly)
}

/// A data representation of the public key
public var rawRepresentation: Data { baseKey.rawRepresentation }

/// Implementation public key object
var bytes: [UInt8] { baseKey.bytes }
}

public struct XonlyKey {
/// Generated secp256k1 x-only public key.
private let baseKey: XonlyKeyImplementation

/// A data representation of the backing x-only public key
public var rawRepresentation: Data { baseKey.rawRepresentation }

/// A boolean that will be set to true if the point encoded by xonly is the
/// negation of the pubkey and set to false otherwise.
public var parity: Bool { baseKey.keyParity.boolValue }

/// Initializes a secp256k1 x-only key for key agreement.
/// - Parameter baseKey: generated secp256k1 public key.
init(baseKey: XonlyKeyImplementation) {
self.baseKey = baseKey
}
}

public struct PrivateKey /*: NISTECPrivateKey */ {
public struct PrivateKey {
let baseKey: PrivateKeyImplementation

/// Creates a random secp256k1 private key for key agreement.
Expand Down Expand Up @@ -78,17 +103,40 @@ public extension secp256k1 {
// MARK: - secp256k1 + DH

extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement {
/// A pointer to a function that hashes an EC point to obtain an ECDH secret
public typealias HashFunctionType = @convention(c) (
UnsafeMutablePointer<UInt8>?,
UnsafePointer<UInt8>?,
UnsafePointer<UInt8>?,
UnsafeMutableRawPointer?
) -> Int32

/// Performs a key agreement with provided public key share.
///
/// - Parameter publicKeyShare: The public key to perform the ECDH with.
/// - Returns: Returns a shared secret
/// - Throws: An error occurred while computing the shared secret
public func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
try sharedSecretFromKeyAgreement(with: publicKeyShare, handler: nil)
}

/// Performs a key agreement with provided public key share.
/// - Parameters:
/// - publicKeyShare: The public key to perform the ECDH with.
/// - handler: Closure for customizing a hash function; Defaults to nil.
/// - 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
) throws -> SharedSecret {
let context = secp256k1.Context.raw
var publicKey = secp256k1_pubkey()
var sharedSecret = [UInt8](repeating: 0, count: 32)

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

Expand Down
39 changes: 16 additions & 23 deletions Sources/implementation/Tweak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,18 @@ public extension secp256k1.Signing.PublicKey {
/// - format: the format of the tweaked `PublicKey` object
/// - Returns: tweaked `PublicKey` object
func add(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
let context = secp256k1.Context.raw
var pubKey = secp256k1_pubkey()
var pubKeyLen = format.length
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
var xonlyKey = secp256k1_xonly_pubkey()
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
var keyParity = Int32()

guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_add(context, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
return try Self(rawRepresentation: pubKeyBytes, format: format)
}

/// Create a new `PublicKey` by multiplying tweak to the public key.
Expand All @@ -93,22 +89,18 @@ public extension secp256k1.Signing.PublicKey {
/// - format: the format of the tweaked `PublicKey` object
/// - Returns: tweaked `PublicKey` object
func multiply(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
let context = secp256k1.Context.raw
var pubKey = secp256k1_pubkey()
var pubKeyLen = format.length
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
var xonlyKey = secp256k1_xonly_pubkey()
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
var keyParity = Int32()

guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_mul(secp256k1.Context.raw, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_mul(context, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
return try Self(rawRepresentation: pubKeyBytes, format: format)
}
}

Expand All @@ -119,17 +111,18 @@ public extension secp256k1.Signing.XonlyKey {
/// - format: the format of the tweaked `XonlyKey` object
/// - Returns: tweaked `PublicKey` object
func add(_ tweak: [UInt8]) throws -> Self {
let context = secp256k1.Context.raw
var pubKey = secp256k1_pubkey()
var inXonlyPubKey = secp256k1_xonly_pubkey()
var outXonlyPubKey = secp256k1_xonly_pubkey()
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
var keyParity = Int32()

guard secp256k1_xonly_pubkey_parse(secp256k1.Context.raw, &inXonlyPubKey, bytes).boolValue,
secp256k1_xonly_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, &inXonlyPubKey, tweak).boolValue,
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &outXonlyPubKey, &keyParity, &pubKey).boolValue,
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &outXonlyPubKey).boolValue,
secp256k1_xonly_pubkey_tweak_add_check(secp256k1.Context.raw, &xonlyBytes, keyParity, &inXonlyPubKey, tweak).boolValue else {
guard secp256k1_xonly_pubkey_parse(context, &inXonlyPubKey, bytes).boolValue,
secp256k1_xonly_pubkey_tweak_add(context, &pubKey, &inXonlyPubKey, tweak).boolValue,
secp256k1_xonly_pubkey_from_pubkey(context, &outXonlyPubKey, &keyParity, &pubKey).boolValue,
secp256k1_xonly_pubkey_serialize(context, &xonlyBytes, &outXonlyPubKey).boolValue,
secp256k1_xonly_pubkey_tweak_add_check(context, &xonlyBytes, keyParity, &inXonlyPubKey, tweak).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

Expand Down
15 changes: 9 additions & 6 deletions Sources/implementation/secp256k1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public extension secp256k1 {
public let rawValue: UInt32
public init(rawValue: UInt32) { self.rawValue = rawValue }
init(rawValue: Int32) { self.rawValue = UInt32(rawValue) }
public static let declassify = Context(rawValue: SECP256K1_CONTEXT_DECLASSIFY)
public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE)
public static let sign = Context(rawValue: SECP256K1_CONTEXT_SIGN)
public static let verify = Context(rawValue: SECP256K1_CONTEXT_VERIFY)
Expand Down Expand Up @@ -164,13 +163,17 @@ extension secp256k1 {

/// A key format representation of the backing public key
@usableFromInline let format: secp256k1.Format

/// Backing initialization that generates a secp256k1 public key from a raw representation.
/// - Parameter data: A raw representation of the key.
@usableFromInline init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {

/// Backing initialization that generates a secp256k1 public key from only a raw representation and key format.
/// - Parameters:
/// - data: A raw representation of the public key.
/// - format: an enum that represents the format of the public key
@usableFromInline init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format) throws {
var keyParity = Int32()

self.bytes = data.bytes
self.format = format
self._xonlyBytes = xonly.bytes
self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: data.bytes, keyParity: &keyParity, format: format)
self._keyParity = keyParity
}

Expand Down
4 changes: 3 additions & 1 deletion Tests/secp256k1Tests/secp256k1Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,11 @@ final class secp256k1Tests: XCTestCase {

let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign1.rawRepresentation)
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign2.rawRepresentation)

let publicKey1 = try! secp256k1.KeyAgreement.PublicKey(rawRepresentation: privateKey1.publicKey.rawRepresentation)

let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey)
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey)
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: publicKey1)

XCTAssertEqual(sharedSecret1.bytes, sharedSecret2.bytes)

Expand Down

0 comments on commit b803887

Please sign in to comment.