Skip to content

Commit

Permalink
Major cleanup of Recovery (#6)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Cyon <alex.cyon@rdx.works>
  • Loading branch information
Sajjon and CyonAlexRDX authored Mar 13, 2023
1 parent 202b5ce commit ca73782
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ extension ECDSASignatureNonRecoverable {
return try Self(rawRepresentation: signatureData)
}

/// `R||S` aka `X9.63` aka `IEEE P1363`
public func p1364() throws -> Data {
try Bridge.compactRepresentationOfSignature(rawRepresentation: _rawRepresentation)
public func compactRepresentation() throws -> Data {
try Bridge.compactRepresentationOfSignature(rawRepresentation: rawRepresentation)
}

public func derRepresentation() throws -> Data {
try Bridge.derRepresentationOfSignature(rawRepresentation: _rawRepresentation)
try Bridge.derRepresentationOfSignature(rawRepresentation: rawRepresentation)
}
}

27 changes: 20 additions & 7 deletions Sources/K1/K1/ECDSA/ECDSASignatureNonRecoverable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@

import Foundation
import CryptoKit
import secp256k1

public struct ECDSASignatureNonRecoverable: Sendable, Hashable, ECSignature {

internal let _rawRepresentation: Data

/// Accepts `R||S` format
public init(p1364: Data) throws {
try self.init(rawRepresentation: swapSignatureByteOrder(p1364))
}
public let rawRepresentation: Data

public init<D: DataProtocol>(rawRepresentation: D) throws {
guard
Expand All @@ -24,7 +20,24 @@ public struct ECDSASignatureNonRecoverable: Sendable, Hashable, ECSignature {
throw K1.Error.incorrectByteCountOfRawSignature
}

self._rawRepresentation = Data(rawRepresentation)
self.rawRepresentation = Data(rawRepresentation)
}

public init<D: DataProtocol>(compactRepresentation: D) throws {
var signature = secp256k1_ecdsa_signature()
let compactBytes = [UInt8](compactRepresentation)
try Bridge.call(ifFailThrow: .failedToParseSignatureFromCompactRepresentation) { context in
secp256k1_ecdsa_signature_parse_compact(
context,
&signature,
compactBytes
)
}

try self.init(rawRepresentation: Data(
bytes: &signature.data,
count: MemoryLayout.size(ofValue: signature.data)
))
}
}

Expand Down
22 changes: 0 additions & 22 deletions Sources/K1/K1/ECDSA/ECDSASignatureRecoverable+Conversion.swift

This file was deleted.

84 changes: 55 additions & 29 deletions Sources/K1/K1/ECDSA/ECDSASignatureRecoverable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,80 @@

import Foundation
import CryptoKit
import secp256k1

public struct ECDSASignatureRecoverable: Sendable, Hashable, ECSignature {

public let rawRepresentation: Data

internal let _rawRepresentation: Data

public init(compactRepresentation: Data, recoveryID: Int32) throws {
guard
compactRepresentation.count == ECDSASignatureNonRecoverable.byteCount
else {
throw K1.Error.incorrectByteCountOfRawSignature
}
var recoverableSignature = secp256k1_ecdsa_recoverable_signature()
let rs = [UInt8](compactRepresentation)

try Bridge.call(ifFailThrow: .failedToParseRecoverableSignatureFromCompactRepresentation) { context in
secp256k1_ecdsa_recoverable_signature_parse_compact(
context,
&recoverableSignature,
rs,
recoveryID
)
}
self.rawRepresentation = Data(
bytes: &recoverableSignature.data,
count: MemoryLayout.size(ofValue: recoverableSignature.data)
)
}

public init<D: DataProtocol>(rawRepresentation: D) throws {

guard
rawRepresentation.count == Self.byteCount
rawRepresentation.count == ECDSASignatureNonRecoverable.byteCount + 1
else {
throw K1.Error.incorrectByteCountOfRawSignature
}

self._rawRepresentation = Data(rawRepresentation)
self.rawRepresentation = Data(rawRepresentation)
}
}


private extension ECDSASignatureRecoverable {
static let byteCount = ECDSASignatureNonRecoverable.byteCount + 1


}

public extension ECDSASignatureRecoverable {

/// `R||S` without `V`
func rs() -> Data {
Data(_rawRepresentation.prefix(64))
}

/// aka Signature `v`, aka `recid`
var recoveryID: Int { Int(_rawRepresentation[64]) }

typealias Scheme = ECDSA
static let scheme: SigningScheme = .ecdsa

func nonRecoverable() throws -> ECDSASignatureNonRecoverable {
try Bridge.convertToNonRecoverable(ecdsaSignature: self)
}

func compact() throws -> (rs: Data, recoveryID: Int) {
var rsBytes = [UInt8](repeating: 0, count: 64)
var recoveryID: Int32 = 0

var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
pointer.copyBytes(
from: rawRepresentation.prefix(pointer.count)
)
}

try Bridge.call(
ifFailThrow: .failedSignatureToConvertRecoverableSignatureToCompact) { context in
secp256k1_ecdsa_recoverable_signature_serialize_compact(
context,
&rsBytes,
&recoveryID,
&recoverableBridgedToC
)
}
return (rs: Data(rsBytes), recoveryID: Int(recoveryID))
}

}

public extension ECDSASignatureRecoverable {
Expand All @@ -63,16 +98,7 @@ public extension ECDSASignatureRecoverable {
static func by<D>(signing hashed: D, with privateKey: K1.PrivateKey, mode: SigningMode) throws -> Self where D : DataProtocol {
try privateKey.ecdsaSignRecoverable(hashed: hashed, mode: mode)
}

// /// Tosses away V byte
// func compactRepresentation() throws -> Data {
//
// }
//
// func derRepresentation() throws -> Data {
// fatalError()
// }


func wasSigned<D>(by signer: K1.PublicKey, for digest: D) throws -> Bool where D : Digest {
try nonRecoverable().wasSigned(by: signer, for: digest)
}
Expand Down
16 changes: 10 additions & 6 deletions Sources/K1/K1/ECDSA/Signature+Format+Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,28 @@ extension Bridge {
pointer.copyBytes(from: rawRepresentation.prefix(pointer.count))
}


try Bridge.call(ifFailThrow: .failedToSerializeCompactSignature) { context in
secp256k1_ecdsa_signature_serialize_compact(context, &compactSignature, &signatureBridgedToC)
secp256k1_ecdsa_signature_serialize_compact(
context,
&compactSignature,
&signatureBridgedToC
)

}

return Data(bytes: &compactSignature, count: compactSignatureLength)
return Data(compactSignature)
}

static func derRepresentationOfSignature(rawRepresentation: Data) throws -> Data {

var signatureBridgedToC = secp256k1_ecdsa_signature()
var derMaxLength = 75 // in fact max is 73, but we can have some margin.
var derSignature = [UInt8](repeating: 0, count: derMaxLength)

withUnsafeMutableBytes(of: &signatureBridgedToC.data) { pointer in
pointer.copyBytes(from: rawRepresentation.prefix(pointer.count))
}

try Bridge.call(ifFailThrow: .failedToSerializeDERSignature) { context in
secp256k1_ecdsa_signature_serialize_der(
context,
Expand All @@ -68,7 +72,7 @@ extension Bridge {
&signatureBridgedToC
)
}

return Data(bytes: &derSignature, count: derMaxLength)
}
}
3 changes: 3 additions & 0 deletions Sources/K1/K1/K1/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public extension K1 {
case failedToECDSASignDigest
case recoverPublicKeyDiscrepancyReceivedSignatureContainingRecoveryIDButFunctionSpecifiesANonMatchingOne
case failedToParseRecoverableSignatureFromECDSASignature
case failedToParseRecoverableSignatureFromCompactRepresentation
case failedToParseSignatureFromCompactRepresentation
case failedSignatureToConvertRecoverableSignatureToCompact
case failedToConvertRecoverableSignatureToNonRecoverable
case failedToRecoverPublicKeyFromSignature
case failedToNormalizeECDSASignature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,6 @@ import secp256k1
import CryptoKit
import Foundation

struct IncorrectByteCount: Swift.Error {}
public func swapSignatureByteOrder<D>(_ data: D) throws -> Data where D: DataProtocol {
guard data.count == 64 || data.count == 65 else {
throw IncorrectByteCount()
}
let invalidByteOrder = Data(data)
let r = Data(invalidByteOrder[0 ..< 32].reversed())
let s = Data(invalidByteOrder[32 ..< 64].reversed())

var vDataOrEmpty = Data()
if data.count > 64 {
vDataOrEmpty = Data([invalidByteOrder[64]])
}

return vDataOrEmpty + r + s
}

extension Bridge {

/// Produces a **recoverable** ECDSA signature.
Expand Down Expand Up @@ -67,7 +50,7 @@ extension Bridge {
nonceFunctionArbitraryBytes
)
}

return Data(
bytes: &signatureRecoverableBridgedToC.data,
count: MemoryLayout.size(ofValue: signatureRecoverableBridgedToC.data)
Expand Down Expand Up @@ -218,15 +201,10 @@ extension Bridge {
ecdsaSignature: ECDSASignatureRecoverable
) throws -> ECDSASignatureNonRecoverable {
var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()
let rs = [UInt8](ecdsaSignature.rs())
try Self.call(
ifFailThrow: .failedToParseRecoverableSignatureFromECDSASignature
) { context in
secp256k1_ecdsa_recoverable_signature_parse_compact(
context,
&recoverableBridgedToC,
rs,
Int32(ecdsaSignature.recoveryID)

withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
pointer.copyBytes(
from: ecdsaSignature.rawRepresentation.prefix(pointer.count)
)
}

Expand Down Expand Up @@ -257,7 +235,7 @@ extension Bridge {
message: [UInt8]
) throws -> [UInt8] {
try _recoverPublicKey(
rs: ecdsaSignature.p1364(),
rs: ecdsaSignature.compactRepresentation(),
recoveryID: recoveryID,
message: message
)
Expand All @@ -268,9 +246,16 @@ extension Bridge {
ecdsaSignature: ECDSASignatureRecoverable,
message: [UInt8]
) throws -> [UInt8] {
try _recoverPublicKey(
rs: ecdsaSignature.rs(),
recoveryID: Int32(ecdsaSignature.recoveryID),
var recoverableBridgedToC = secp256k1_ecdsa_recoverable_signature()

withUnsafeMutableBytes(of: &recoverableBridgedToC.data) { pointer in
pointer.copyBytes(
from: ecdsaSignature.rawRepresentation.prefix(pointer.count)
)
}

return try __recoverPubKeyFrom(
signatureBridgedToC: recoverableBridgedToC,
message: message
)
}
Expand All @@ -283,6 +268,7 @@ extension Bridge {
) throws -> [UInt8] {
var signatureBridgedToC = secp256k1_ecdsa_recoverable_signature()
let rs = [UInt8](rsData)

try Self.call(
ifFailThrow: .failedToParseRecoverableSignatureFromECDSASignature
) { context in
Expand All @@ -294,6 +280,17 @@ extension Bridge {
)
}

return try __recoverPubKeyFrom(
signatureBridgedToC: signatureBridgedToC,
message: message
)
}

static func __recoverPubKeyFrom(
signatureBridgedToC: secp256k1_ecdsa_recoverable_signature,
message: [UInt8]
) throws -> [UInt8] {
var signatureBridgedToC = signatureBridgedToC
var publicKeyBridgedToC = secp256k1_pubkey()
try Self.call(
ifFailThrow: .failedToRecoverPublicKeyFromSignature
Expand Down Expand Up @@ -378,9 +375,9 @@ public extension ECDSASignatureNonRecoverable {
wrapped: .init(uncompressedRaw: uncompressedPublicKeyBytes)
)

// guard try publicKey.isValid(signature: self, hashed: messageThatWasSigned) else {
// throw K1.Error.expectedPublicKeyToBeValidForSignatureAndMessage
// }
guard try publicKey.isValid(signature: self, hashed: messageThatWasSigned) else {
throw K1.Error.expectedPublicKeyToBeValidForSignatureAndMessage
}

return publicKey
}
Expand All @@ -394,13 +391,11 @@ public extension K1.PrivateKey {
mode: ECDSASignatureNonRecoverable.SigningMode = .default
) throws -> ECDSASignatureRecoverable {
let messageBytes = [UInt8](message)
let signatureData = try withSecureBytes { (secureBytes: SecureBytes) -> Data in
try Bridge.ecdsaSignRecoverable(message: messageBytes, privateKey: secureBytes, mode: mode)
let raw = try withSecureBytes {
try Bridge.ecdsaSignRecoverable(message: messageBytes, privateKey: $0, mode: mode)
}

return try ECDSASignatureRecoverable(
rawRepresentation: signatureData
)
return try ECDSASignatureRecoverable.init(rawRepresentation: raw)
}

/// Produces a **non recoverable** ECDSA signature.
Expand Down
Loading

0 comments on commit ca73782

Please sign in to comment.