diff --git a/Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h b/Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h index ac26435d..e00a3a24 100644 --- a/Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h +++ b/Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h @@ -101,6 +101,8 @@ BIGNUM *CCryptoBoringSSLShims_BN_bin2bn(const void *in, size_t len, BIGNUM *ret) size_t CCryptoBoringSSLShims_BN_bn2bin(const BIGNUM *in, void *out); +int CCryptoBoringSSLShims_BN_mod(BIGNUM *rem, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); + int CCryptoBoringSSLShims_RSA_verify(int hash_nid, const void *msg, size_t msg_len, const void *sig, size_t sig_len, RSA *rsa); diff --git a/Sources/CCryptoBoringSSLShims/shims.c b/Sources/CCryptoBoringSSLShims/shims.c index 051fe100..60925ba2 100644 --- a/Sources/CCryptoBoringSSLShims/shims.c +++ b/Sources/CCryptoBoringSSLShims/shims.c @@ -118,6 +118,10 @@ size_t CCryptoBoringSSLShims_BN_bn2bin(const BIGNUM *in, void *out) { return CCryptoBoringSSL_BN_bn2bin(in, out); } +int CCryptoBoringSSLShims_BN_mod(BIGNUM *rem, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx) { + return BN_mod(rem, a, m, ctx); +} + int CCryptoBoringSSLShims_RSA_verify(int hash_nid, const void *msg, size_t msg_len, const void *sig, size_t sig_len, RSA *rsa) { return CCryptoBoringSSL_RSA_verify(hash_nid, msg, msg_len, sig, sig_len, rsa); diff --git a/Sources/CryptoBoringWrapper/ArbitraryPrecisionInteger.swift b/Sources/CryptoBoringWrapper/ArbitraryPrecisionInteger.swift index 495dc70c..f465b7d7 100644 --- a/Sources/CryptoBoringWrapper/ArbitraryPrecisionInteger.swift +++ b/Sources/CryptoBoringWrapper/ArbitraryPrecisionInteger.swift @@ -98,6 +98,14 @@ extension ArbitraryPrecisionInteger { public init(bytes: Bytes) throws { self._backing = try BackingStorage(bytes: bytes) } + + /// Create an `ArbitraryPrecisionInteger` from a hex string. + /// + /// - Parameter hexString: Hex byte string (big-endian, no `0x` prefix, may start with `-` for a negative number). + @inlinable + public init(hexString: String) throws { + self._backing = try BackingStorage(hexString: hexString) + } } extension ArbitraryPrecisionInteger.BackingStorage { @@ -112,8 +120,27 @@ extension ArbitraryPrecisionInteger.BackingStorage { throw CryptoBoringWrapperError.internalBoringSSLError() } } + + @inlinable + convenience init(hexString: String) throws { + self.init() + try hexString.withCString { hexStringPtr in + /// `BN_hex2bin` takes a `BIGNUM **` so we need a double WUMP dance. + try withUnsafeMutablePointer(to: &self._backing) { backingPtr in + var backingPtr: UnsafeMutablePointer? = backingPtr + try withUnsafeMutablePointer(to: &backingPtr) { backingPtrPtr in + /// `BN_hex2bin` returns the number of bytes of `in` processed or zero on error. + guard CCryptoBoringSSL_BN_hex2bn(backingPtrPtr, hexStringPtr) == hexString.count else { + throw CryptoBoringWrapperError.incorrectParameterSize + } + } + } + } + } } + + // MARK: - Pointer helpers extension ArbitraryPrecisionInteger { @@ -381,6 +408,49 @@ extension ArbitraryPrecisionInteger: SignedNumeric { } } +// MARK: - Other arithmetic operations + +extension ArbitraryPrecisionInteger { + @inlinable + public static func gcd(_ a: ArbitraryPrecisionInteger, _ b: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + var result = ArbitraryPrecisionInteger() + + guard result.withUnsafeMutableBignumPointer({ resultPtr in + a.withUnsafeBignumPointer { aPtr in + b.withUnsafeBignumPointer { bPtr in + ArbitraryPrecisionInteger.withUnsafeBN_CTX { bnCtx in + CCryptoBoringSSL_BN_gcd(resultPtr, aPtr, bPtr, bnCtx) + } + } + } + }) == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return result + } + + @inlinable + public func isCoprime(with other: ArbitraryPrecisionInteger) throws -> Bool { + try Self.gcd(self, other) == 1 + } + + @inlinable + public static func random(inclusiveMin: UInt64, exclusiveMax: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + var result = ArbitraryPrecisionInteger() + + guard result.withUnsafeMutableBignumPointer({ resultPtr in + exclusiveMax.withUnsafeBignumPointer { exclusiveMaxPtr in + CCryptoBoringSSL_BN_rand_range_ex(resultPtr, inclusiveMin, exclusiveMaxPtr) + } + }) == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return result + } +} + // MARK: - Serializing extension Data { @@ -408,6 +478,12 @@ extension Data { assert(written == byteCount) } + + @inlinable + public init(bytesOf integer: ArbitraryPrecisionInteger, paddedToSize paddingSize: Int) throws { + self.init(capacity: paddingSize) + try self.append(bytesOf: integer, paddedToSize: paddingSize) + } } // MARK: - Printing diff --git a/Sources/CryptoBoringWrapper/FiniteFieldArithmeticContext.swift b/Sources/CryptoBoringWrapper/FiniteFieldArithmeticContext.swift index cded39a5..4e219f39 100644 --- a/Sources/CryptoBoringWrapper/FiniteFieldArithmeticContext.swift +++ b/Sources/CryptoBoringWrapper/FiniteFieldArithmeticContext.swift @@ -48,6 +48,24 @@ public final class FiniteFieldArithmeticContext { // MARK: - Arithmetic operations extension FiniteFieldArithmeticContext { + @inlinable + public func residue(_ x: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + var result = ArbitraryPrecisionInteger() + + guard x.withUnsafeBignumPointer({ xPtr in + self.fieldSize.withUnsafeBignumPointer { modPtr in + result.withUnsafeMutableBignumPointer { resultPtr in + CCryptoBoringSSL_BN_nnmod(resultPtr, xPtr, modPtr, self.bnCtx) + } + } + }) == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return result + + } + @inlinable public func square(_ input: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { var output = ArbitraryPrecisionInteger() @@ -151,4 +169,78 @@ extension FiniteFieldArithmeticContext { return try ArbitraryPrecisionInteger(copying: actualOutputPointer) } + + @inlinable + public func inverse(_ x: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger? { + var result = ArbitraryPrecisionInteger() + + guard result.withUnsafeMutableBignumPointer({ resultPtr in + x.withUnsafeBignumPointer { xPtr in + self.fieldSize.withUnsafeBignumPointer { modPtr in + CCryptoBoringSSL_BN_mod_inverse(resultPtr, xPtr , modPtr, self.bnCtx) + } + } + }) != nil else { return nil } + + return result + } + + @inlinable + public func pow(_ x: ArbitraryPrecisionInteger, _ p: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + try self.pow(x, p) { r, x, p, m, ctx, _ in CCryptoBoringSSL_BN_mod_exp(r, x, p, m, ctx) } + } + + @inlinable + public func pow(secret x: ArbitraryPrecisionInteger, _ p: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + guard x < self.fieldSize else { throw CryptoBoringWrapperError.incorrectParameterSize } + return try self.pow(x, p, using: CCryptoBoringSSL_BN_mod_exp_mont) + } + + @inlinable + public func pow(secret x: ArbitraryPrecisionInteger, secret p: ArbitraryPrecisionInteger) throws -> ArbitraryPrecisionInteger { + guard x < self.fieldSize else { throw CryptoBoringWrapperError.incorrectParameterSize } + return try self.pow(x, p, using: CCryptoBoringSSL_BN_mod_exp_mont_consttime) + } + + /* private but @usableFromInline */ @usableFromInline func pow( + _ a: ArbitraryPrecisionInteger, + _ b: ArbitraryPrecisionInteger, + using method: ( + _ rr: UnsafeMutablePointer?, + _ a: UnsafePointer?, + _ p: UnsafePointer?, + _ m: UnsafePointer?, + _ ctx: OpaquePointer?, + _ mont: UnsafePointer? + ) -> Int32 + ) throws -> ArbitraryPrecisionInteger { + var result = ArbitraryPrecisionInteger() + + guard result.withUnsafeMutableBignumPointer({ resultPtr in + a.withUnsafeBignumPointer { aPtr in + b.withUnsafeBignumPointer { bPtr in + self.fieldSize.withUnsafeBignumPointer { modPtr in + self.withUnsafeBN_MONT_CTX { montCtxPtr in + method(resultPtr, aPtr, bPtr, modPtr, self.bnCtx, montCtxPtr) + } + } + } + } + }) == 1 else { + throw CryptoBoringWrapperError.internalBoringSSLError() + } + + return result + } + + /// Some functions require a `BN_MONT_CTX` parameter: this obtains one for the field modulus with a scoped lifetime. + /* private but @usableFromInline */ @usableFromInline func withUnsafeBN_MONT_CTX(_ body: (UnsafePointer) throws -> T) rethrows -> T { + try self.fieldSize.withUnsafeBignumPointer { modPtr in + // We force unwrap here because this call can only fail if the allocator is broken, and if + // the allocator fails we don't have long to live anyway. + let montCtx = CCryptoBoringSSL_BN_MONT_CTX_new_for_modulus(modPtr, self.bnCtx)! + defer { CCryptoBoringSSL_BN_MONT_CTX_free(montCtx) } + return try body(montCtx) + } + } } diff --git a/Sources/_CryptoExtras/CMakeLists.txt b/Sources/_CryptoExtras/CMakeLists.txt index d81d942d..d59ad4d5 100644 --- a/Sources/_CryptoExtras/CMakeLists.txt +++ b/Sources/_CryptoExtras/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(_CryptoExtras "ChaCha20CTR/BoringSSL/ChaCha20CTR_boring.swift" "ChaCha20CTR/ChaCha20CTR.swift" + "RSA/RSA+BlindSigning.swift" "RSA/RSA.swift" "RSA/RSA_boring.swift" "RSA/RSA_security.swift" diff --git a/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift b/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift new file mode 100644 index 00000000..35ca5bf9 --- /dev/null +++ b/Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift @@ -0,0 +1,456 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.md for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import Foundation +import Crypto + +// NOTE: RSABSSA API is implemented using BoringSSL on all platforms. +fileprivate typealias BackingPublicKey = BoringSSLRSAPublicKey +fileprivate typealias BackingPrivateKey = BoringSSLRSAPrivateKey + +extension _RSA { + public enum BlindSigning {} +} + +extension _RSA.BlindSigning { + public struct PublicKey: Sendable { + public typealias Parameters = _RSA.BlindSigning.Parameters + + private var backing: BackingPublicKey + private let parameters: Parameters + + /// Construct an RSA public key from a PEM representation. + /// + /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + public init(pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA public key from a PEM representation. + /// + /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. + public init(unsafePEMRepresentation pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 1024 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA public key from a DER representation. + /// + /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + public init(derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA public key from a DER representation. + /// + /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. + public init(unsafeDERRepresentation derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPublicKey(derRepresentation: derRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 1024 else { + throw CryptoKitError.incorrectParameterSize + } + } + + public var pkcs1DERRepresentation: Data { + self.backing.pkcs1DERRepresentation + } + + public var pkcs1PEMRepresentation: String { + self.backing.pkcs1PEMRepresentation + } + + public var derRepresentation: Data { + self.backing.derRepresentation + } + + public var pemRepresentation: String { + self.backing.pemRepresentation + } + + public var keySizeInBits: Int { + self.backing.keySizeInBits + } + + fileprivate init(_ backing: BackingPublicKey, _ parameters: Parameters) { + self.backing = backing + self.parameters = parameters + } + } +} + +extension _RSA.BlindSigning { + public struct PrivateKey: Sendable { + public typealias Parameters = _RSA.BlindSigning.Parameters + + private var backing: BackingPrivateKey + private let parameters: Parameters + + /// Construct an RSA private key from a PEM representation. + /// + /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + public init(pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA private key from a PEM representation. + /// + /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. + public init(unsafePEMRepresentation pemRepresentation: String, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPrivateKey(pemRepresentation: pemRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 1024 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA private key from a DER representation. + /// + /// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + public init(derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Construct an RSA private key from a DER representation. + /// + /// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate + /// for their use-case. + /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. + public init(unsafeDERRepresentation derRepresentation: Bytes, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + self.backing = try BackingPrivateKey(derRepresentation: derRepresentation) + self.parameters = parameters + + guard self.keySizeInBits >= 1024 else { + throw CryptoKitError.incorrectParameterSize + } + } + + /// Randomly generate a new RSA private key of a given size. + /// + /// This constructor will refuse to generate keys smaller than 2048 bits. Callers that want to enforce minimum + /// key size requirements should validate `keySize` before use. + public init(keySize: _RSA.Signing.KeySize, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + guard keySize.bitCount >= 2048 else { + throw CryptoKitError.incorrectParameterSize + } + self.backing = try BackingPrivateKey(keySize: keySize) + self.parameters = parameters + } + + /// Randomly generate a new RSA private key of a given size. + /// + /// This constructor will refuse to generate keys smaller than 1024 bits. Callers that want to enforce minimum + /// key size requirements should validate `unsafekeySize` before use. + /// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons. + public init(unsafeKeySize keySize: _RSA.Signing.KeySize, parameters: Parameters = .RSABSSA_SHA384_PSS_Randomized) throws { + guard keySize.bitCount >= 1024 else { + throw CryptoKitError.incorrectParameterSize + } + self.backing = try BackingPrivateKey(keySize: keySize) + self.parameters = parameters + } + + public var derRepresentation: Data { + self.backing.derRepresentation + } + + public var pemRepresentation: String { + self.backing.pemRepresentation + } + + public var pkcs8PEMRepresentation: String { + self.backing.pkcs8PEMRepresentation + } + + public var keySizeInBits: Int { + self.backing.keySizeInBits + } + + public var publicKey: _RSA.BlindSigning.PublicKey { + _RSA.BlindSigning.PublicKey(self.backing.publicKey, self.parameters) + } + } +} + +extension _RSA.BlindSigning { + public struct BlindSignature: Sendable, ContiguousBytes { + public var rawRepresentation: Data + + public init(rawRepresentation: D) { + self.rawRepresentation = Data(rawRepresentation) + } + + internal init(signatureBytes: [UInt8]) { + self.rawRepresentation = Data(signatureBytes) + } + + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try self.rawRepresentation.withUnsafeBytes(body) + } + } +} + +extension _RSA.BlindSigning { + /// Parameters used in the blind signing protocol. + /// + /// Users cannot create parameters manually and should use one of the static properties for + /// a standard RSABSSA variant. + /// + /// The RECOMMENDED variants are RSABSSA-SHA384-PSS-Randomized or RSABSSA-SHA384-PSSZERO-Randomized. + /// + /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). + public struct Parameters: Sendable { + enum Padding { case PSS, PSSZERO } + var padding: Padding + + enum Preparation { case identity, randomized } + var preparation: Preparation + + var saltLength: Int32 { + switch self.padding { + case .PSS: return Int32(H.Digest.byteCount) + case .PSSZERO: return 0 + } + } + } +} + +extension _RSA.BlindSigning.Parameters where H == SHA384 { + /// RSABSSA-SHA384-PSS-Randomized + /// + /// This named variant uses SHA-384 as the EMSA-PSS Hash option, MGF1 with SHA-384 as the EMSA-PSS MGF option, + /// and 48 as the EMSA-PSS sLen option (48-byte salt length); it also uses the randomized preparation function. + /// + /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). + public static let RSABSSA_SHA384_PSS_Randomized = Self(padding: .PSS, preparation: .randomized) + + /// RSABSSA-SHA384-PSSZERO-Randomized + /// + /// This named variant uses SHA-384 as the EMSA-PSS Hash option, MGF1 with SHA-384 as the EMSA-PSS MGF option, + /// and 0 as the EMSA-PSS sLen option (0-byte salt length); it also uses the randomized preparation function. + /// + /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). + public static let RSABSSA_SHA384_PSSZERO_Randomized = Self(padding: .PSSZERO, preparation: .randomized) + + /// RSABSSA-SHA384-PSS-Deterministic + /// + /// This named variant uses SHA-384 as the EMSA-PSS Hash option, MGF1 with SHA-384 as the EMSA-PSS MGF option, + /// and 48 as the EMSA-PSS sLen option (48-byte salt length); it also uses the identity preparation function. + /// + /// - WARNING: Not all named variants can be used interchangeably. In particular, applications that provide + /// high-entropy input messages can safely use named variants without randomized message preparation, as the + /// additional message randomization does not offer security advantages. + /// For all other applications, the variants that use the randomized preparation function protect clients from + /// malicious signers. + /// + /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). + public static let RSABSSA_SHA384_PSS_Deterministic = Self(padding: .PSS, preparation: .identity) + + /// RSABSSA-SHA384-PSSZERO-Deterministic + /// + /// This named variant uses SHA-384 as the EMSA-PSS Hash option, MGF1 with SHA-384 as the EMSA-PSS MGF option, + /// and 0 as the EMSA-PSS sLen option (0-byte salt length); it also uses the identity preparation function + /// + /// - NOTE: This is the only variant that produces deterministic signatures over the client's input message. + /// + /// - WARNING: Applications that require deterministic signatures can use the RSABSSA-SHA384-PSSZERO-Deterministic + /// variant, but only if their input messages have high entropy. Applications that use + /// RSABSSA-SHA384-PSSZERO-Deterministic SHOULD carefully analyze the security implications, taking into account + /// the possibility of adversarially generated signer keys as described in Section 7.3. When it is not clear whether + /// an application requires deterministic or randomized signatures, applications SHOULD use one of the variants with + /// randomized message preparation. + /// + /// - Seealso: [RFC 9474: RSABSSA Variants](https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants). + public static let RSABSSA_SHA384_PSSZERO_Deterministic = Self(padding: .PSSZERO, preparation: .identity) +} + +extension _RSA.BlindSigning { + /// An input ready to be blinded, possibly prepended with random bytes. + /// + /// Users cannot create values of this type manually; it is created and returned by the prepare operation. + public struct PreparedMessage { + var rawRepresentation: Data + } + + /// The blinding inverse for a blinded message, used to unblind a blind signature. + /// + /// Users cannot create values of this type manually; it is created and returned by the blind operation. + public struct BlindingInverse { + var rawRepresentation: Data + } + + /// The blinded message and its blinding inverse for unblinding its blind signature. + /// + /// Users cannot create values of this type manually; it is created and returned by the blind operation. + public struct BlindingResult { + /// Blinded message to be sent to the issuer. + public var blindedMessage: Data + + /// Blinding inverse for producing a signature for the prepared message from the blinded signature. + public var inverse: BlindingInverse + } +} + +extension _RSA.BlindSigning.PrivateKey { + /// Generate a blind signature with the given key for a blinded message. + /// + /// - Parameter message: The blinded message to sign. + /// - Returns: A blind signature. + /// - Throws: If there is a failure producing the signature. + /// + /// - Seealso: [RFC 9474: BlindSign](https://www.rfc-editor.org/rfc/rfc9474.html#name-blindsign). + public func blindSignature(for message: D) throws -> _RSA.BlindSigning.BlindSignature { + try self.backing.blindSignature(for: message) + } +} + +extension _RSA.BlindSigning.PublicKey { + /// Prepare a message to be signed using the blind signing protocol. + /// + /// - Parameter message: The message to be signed. + /// - Parameter parameters: Parameters used in the blind signing protocol. + /// - Returns: A prepared message, modified according to the parameters provided. + /// + /// - Seealso: [RFC 9474: Prepare](https://www.rfc-editor.org/rfc/rfc9474.html#name-prepare). + public func prepare(_ message: D) -> _RSA.BlindSigning.PreparedMessage { + switch self.parameters.preparation { + case .identity: + return _RSA.BlindSigning.PreparedMessage(rawRepresentation: Data(message)) + case .randomized: + var preparedMessageBytes = Data(capacity: 32 + message.count) + preparedMessageBytes.append(contentsOf: SystemRandomNumberGenerator.randomBytes(count: 32)) + preparedMessageBytes.append(contentsOf: message) + return _RSA.BlindSigning.PreparedMessage(rawRepresentation: preparedMessageBytes) + } + } + + /// Blind a message to be signed by the server using the blind signing protocol. + /// + /// - Parameter message: The message to be signed. + /// - Returns: The blinded message, and its inverse for unblinding its blind signature. + /// + /// - Seealso: [RFC 9474: Blind](https://www.rfc-editor.org/rfc/rfc9474.html#name-blind). + public func blind(_ message: _RSA.BlindSigning.PreparedMessage) throws -> _RSA.BlindSigning.BlindingResult { + try self.backing.blind(message, parameters: self.parameters) + } + + /// Unblinds the message and produce a signature for the message. + /// + /// - Parameter signature: The signature of the blinded message. + /// - Parameter message: The message to be signed. + /// - Parameter blindInverse: The inverse from the message blinding. + /// - Returns: The signature of the message. + /// + /// - Seealso: [RFC 9474: Finalize](https://www.rfc-editor.org/rfc/rfc9474.html#name-finalize). + public func finalize( + _ signature: _RSA.BlindSigning.BlindSignature, + for message: _RSA.BlindSigning.PreparedMessage, + blindingInverse: _RSA.BlindSigning.BlindingInverse + ) throws -> _RSA.Signing.RSASignature { + try self.backing.finalize(signature, for: message, blindingInverse: blindingInverse, parameters: self.parameters) + } + + /// Validate a signature for a prepared message. + /// + /// - Parameter signature: The signature to verify. + /// - Parameter message: The prepared message used in the blind signature protocol. + /// - Returns: True if the signature is valid; false otherwise. + /// + /// - Seealso: [RFC 9474: Verification](https://www.rfc-editor.org/rfc/rfc9474.html#name-verification). + public func isValidSignature( + _ signature: _RSA.Signing.RSASignature, + for message: _RSA.BlindSigning.PreparedMessage + ) -> Bool { + switch parameters.padding { + case .PSS: + return self.backing.isValidSignature(signature, for: H.hash(data: message.rawRepresentation), padding: .PSS) + case .PSSZERO: + return self.backing.isValidSignature(signature, for: H.hash(data: message.rawRepresentation), padding: .PSSZERO) + } + } +} + +extension _RSA.BlindSigning { + /// Errors defined in the RSA Blind Signatures protocol. + /// + /// - NOTE: This type does not conform to `Swift.Error`, it is used to construct a `CryptoKitError`. + /// + /// - Seealso: [RFC 9474: Errors](https://www.rfc-editor.org/rfc/rfc9474.html#name-errors). + enum ProtocolError { + case messageTooLong + case encodingError + case invalidInput + case signingFailure + case messageRepresentativeOutOfRange + case invalidSignature + case unexpectedInputSize + } +} + +extension CryptoKitError { + /// Map an error from the RSA Blind Signatures protocol to a CryptoKitError. + init(_ error: _RSA.BlindSigning.ProtocolError) { + switch error { + case .messageTooLong: + self = .incorrectParameterSize + case .encodingError: + self = .incorrectParameterSize + case .invalidInput: + self = .incorrectParameterSize + case .signingFailure: + self = .authenticationFailure + case .messageRepresentativeOutOfRange: + self = .incorrectParameterSize + case .invalidSignature: + self = .authenticationFailure + case .unexpectedInputSize: + self = .incorrectParameterSize + } + } +} diff --git a/Sources/_CryptoExtras/RSA/RSA.swift b/Sources/_CryptoExtras/RSA/RSA.swift index fbcec8ca..5baa1139 100644 --- a/Sources/_CryptoExtras/RSA/RSA.swift +++ b/Sources/_CryptoExtras/RSA/RSA.swift @@ -246,6 +246,7 @@ extension _RSA.Signing { internal enum Backing { case pkcs1v1_5 case pss + case pssZero // NOTE: this is internal-only, for RSABSSA. } internal var backing: Backing @@ -266,6 +267,13 @@ extension _RSA.Signing { /// /// MGF1 is parameterised with a hash function. The salt length will be the size of the digest from the given hash function. public static let PSS = Self(.pss) + + /// PSS padding using MGF1, with zero-length salt. + /// + /// MGF1 is parameterised with a hash function. The salt length is overriden to be zero. + /// + /// - NOTE: This is not API and is only accessible through the RSA Blind Signatures API. + internal static let PSSZERO = Self(.pssZero) } } diff --git a/Sources/_CryptoExtras/RSA/RSA_boring.swift b/Sources/_CryptoExtras/RSA/RSA_boring.swift index 0787b6af..368ce5fd 100644 --- a/Sources/_CryptoExtras/RSA/RSA_boring.swift +++ b/Sources/_CryptoExtras/RSA/RSA_boring.swift @@ -14,11 +14,10 @@ import Foundation import Crypto -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -// Nothing; this is implemented in RSA_security -#else +// NOTE: This file is unconditionally compiled because RSABSSA is implemented using BoringSSL on all platforms. import CCryptoBoringSSL import CCryptoBoringSSLShims +import CryptoBoringWrapper internal struct BoringSSLRSAPublicKey: Sendable { private var backing: Backing @@ -101,6 +100,10 @@ extension BoringSSLRSAPrivateKey { internal func decrypt(_ data: D, padding: _RSA.Encryption.Padding) throws -> Data { return try self.backing.decrypt(data, padding: padding) } + + internal func blindSignature(for message: D) throws -> _RSA.BlindSigning.BlindSignature { + return try self.backing.blindSignature(for: message) + } } extension BoringSSLRSAPublicKey { @@ -111,6 +114,22 @@ extension BoringSSLRSAPublicKey { internal func encrypt(_ data: D, padding: _RSA.Encryption.Padding) throws -> Data { return try self.backing.encrypt(data, padding: padding) } + + internal func blind( + _ message: _RSA.BlindSigning.PreparedMessage, + parameters: _RSA.BlindSigning.Parameters + ) throws -> _RSA.BlindSigning.BlindingResult { + return try self.backing.blind(message, parameters: parameters) + } + + internal func finalize( + _ signature: _RSA.BlindSigning.BlindSignature, + for message: _RSA.BlindSigning.PreparedMessage, + blindingInverse: _RSA.BlindSigning.BlindingInverse, + parameters: _RSA.BlindSigning.Parameters + ) throws -> _RSA.Signing.RSASignature { + return try self.backing.finalize(signature, for: message, blindingInverse: blindingInverse, parameters: parameters) + } } extension BoringSSLRSAPublicKey { @@ -264,6 +283,17 @@ extension BoringSSLRSAPublicKey { signaturePtr.baseAddress, signaturePtr.count ) + case .pssZero: + return CCryptoBoringSSLShims_RSA_verify_pss_mgf1( + rsaPublicKey, + digestPtr.baseAddress, + digestPtr.count, + hashDigestType.dispatchTable, + hashDigestType.dispatchTable, + CInt(0), + signaturePtr.baseAddress, + signaturePtr.count + ) } } return rc == 1 @@ -315,6 +345,94 @@ extension BoringSSLRSAPublicKey { return output } + fileprivate func blind( + _ message: _RSA.BlindSigning.PreparedMessage, + parameters: _RSA.BlindSigning.Parameters + ) throws -> _RSA.BlindSigning.BlindingResult { + let rsaPublicKey = CCryptoBoringSSL_EVP_PKEY_get0_RSA(self.pointer) + let modulusByteCount = Int(CCryptoBoringSSL_RSA_size(rsaPublicKey)) + let e = try ArbitraryPrecisionInteger(copying: CCryptoBoringSSL_RSA_get0_e(rsaPublicKey)) + let n = try ArbitraryPrecisionInteger(copying: CCryptoBoringSSL_RSA_get0_n(rsaPublicKey)) + let finiteField = try FiniteFieldArithmeticContext(fieldSize: n) + + // 1. encoded_msg = EMSA-PSS-ENCODE(msg, bit_len(n)) with Hash, MGF, and salt_len as defined in the parameters + // 2. If EMSA-PSS-ENCODE raises an error, re-raise the error and stop + // 3. m = bytes_to_int(encoded_msg) + let m = try BlindSigningHelpers.EMSAPSSEncode( + rsaPublicKey: rsaPublicKey, + modulusByteCount: modulusByteCount, + message: message, + parameters: parameters + ) + + // 4. c = is_coprime(m, n) + let c = try m.isCoprime(with: n) + + // 5. If c is false, raise an "invalid input" error and stop + if !c { throw CryptoKitError(_RSA.BlindSigning.ProtocolError.invalidInput) } + + // 6. r = random_integer_uniform(1, n) + // 7. inv = inverse_mod(r, n) + // 8. If inverse_mod fails, raise a "blinding error" error and stop + // NOTE: We retry here until we get an appropriate r, which is suggested. + var r: ArbitraryPrecisionInteger + var inv: ArbitraryPrecisionInteger! + repeat { + r = try ArbitraryPrecisionInteger.random(inclusiveMin: 1, exclusiveMax: n) + inv = try finiteField.inverse(r) + } while inv == nil + + // 9. x = RSAVP1(pk, r) + let x = try finiteField.pow(secret: r, e) + + // 10. z = (m * x) mod n + let z = try finiteField.multiply(m, x) + + // 11. blinded_msg = int_to_bytes(z, modulus_len) + let blindedMessage = try Data(bytesOf: z, paddedToSize: modulusByteCount) + + // 12. output blinded_msg, inv + let blindingInverse = _RSA.BlindSigning.BlindingInverse(rawRepresentation: try Data(bytesOf: inv, paddedToSize: modulusByteCount)) + return _RSA.BlindSigning.BlindingResult(blindedMessage: blindedMessage, inverse: blindingInverse) + } + + fileprivate func finalize( + _ blindSignature: _RSA.BlindSigning.BlindSignature, + for message: _RSA.BlindSigning.PreparedMessage, + blindingInverse: _RSA.BlindSigning.BlindingInverse, + parameters: _RSA.BlindSigning.Parameters + ) throws -> _RSA.Signing.RSASignature { + let rsaPublicKey = CCryptoBoringSSL_EVP_PKEY_get0_RSA(self.pointer) + let modulusByteCount = Int(CCryptoBoringSSL_RSA_size(rsaPublicKey)) + let n = try ArbitraryPrecisionInteger(copying: CCryptoBoringSSL_RSA_get0_n(rsaPublicKey)) + let finiteField = try FiniteFieldArithmeticContext(fieldSize: n) + + // 1. If len(blind_sig) != modulus_len, raise an "unexpected input size" error and stop + guard blindSignature.rawRepresentation.count == modulusByteCount else { + throw CryptoKitError(_RSA.BlindSigning.ProtocolError.unexpectedInputSize) + } + + // 2. z = bytes_to_int(blind_sig) + let z = try ArbitraryPrecisionInteger(bytes: blindSignature.rawRepresentation) + + // 3. s = (z * inv) mod n + let inv = try ArbitraryPrecisionInteger(bytes: blindingInverse.rawRepresentation) + let s = try finiteField.multiply(z, inv) + + // 4. sig = int_to_bytes(s, modulus_len) + let sig = _RSA.Signing.RSASignature(rawRepresentation: try Data(bytesOf: s, paddedToSize: modulusByteCount)) + + // 5. result = RSASSA-PSS-VERIFY(pk, msg, sig) with Hash, MGF, and salt_len as defined in the parameters + let result = try BlindSigningHelpers.RSASSAPSSVerify(rsaPublicKey: rsaPublicKey, modulusByteCount: modulusByteCount, message: message, signature: sig, parameters: parameters) + + // 6. If result = "valid signature", output sig, else raise an "invalid signature" error and stop + if result { + return sig + } else { + throw CryptoKitError(_RSA.BlindSigning.ProtocolError.invalidSignature) + } + } + deinit { CCryptoBoringSSL_EVP_PKEY_free(self.pointer) } @@ -505,6 +623,18 @@ extension BoringSSLRSAPrivateKey { hashDigestType.dispatchTable, CInt(hashDigestType.digestLength) ) + case .pssZero: + return CCryptoBoringSSLShims_RSA_sign_pss_mgf1( + rsaPrivateKey, + &outputLength, + bufferPtr.baseAddress, + bufferPtr.count, + digestPtr.baseAddress, + digestPtr.count, + hashDigestType.dispatchTable, + hashDigestType.dispatchTable, + CInt(0) + ) } } if rc != 1 { @@ -564,9 +694,149 @@ extension BoringSSLRSAPrivateKey { return output } + fileprivate func blindSignature(for message: D) throws -> _RSA.BlindSigning.BlindSignature { + let rsaPrivateKey = CCryptoBoringSSL_EVP_PKEY_get0_RSA(self.pointer) + let signatureByteCount = Int(CCryptoBoringSSL_RSA_size(rsaPrivateKey)) + + guard message.count == signatureByteCount else { + throw CryptoKitError.incorrectParameterSize + } + + let messageBytes: ContiguousBytes = message.regions.count == 1 ? message.regions.first! : Array(message) + + let signature = try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: signatureByteCount) { signatureBufferPtr in + try messageBytes.withUnsafeBytes { messageBufferPtr in + /// NOTE: BoringSSL promotes the use of `RSA_sign_raw` over `RSA_private_encrypt`. + var outputCount = 0 + guard CCryptoBoringSSL_RSA_sign_raw( + rsaPrivateKey, + &outputCount, + signatureBufferPtr.baseAddress, + signatureBufferPtr.count, + messageBufferPtr.baseAddress, + messageBufferPtr.count, + RSA_NO_PADDING + ) == 1 else { + switch ERR_GET_REASON(CCryptoBoringSSL_ERR_peek_last_error()) { + case RSA_R_DATA_TOO_LARGE_FOR_MODULUS: + throw CryptoKitError(_RSA.BlindSigning.ProtocolError.messageRepresentativeOutOfRange) + default: + throw CryptoKitError.internalBoringSSLError() + } + } + precondition(outputCount == signatureBufferPtr.count) + } + return _RSA.BlindSigning.BlindSignature(rawRepresentation: Data(signatureBufferPtr)) + } + + // NOTE: Verification is part of the specification. + try self.verifyBlindSignature(signature, for: messageBytes) + + return signature + } + + fileprivate func verifyBlindSignature(_ signature: _RSA.BlindSigning.BlindSignature, for blindedMessage: D) throws { + try signature.withUnsafeBytes { signatureBufferPtr in + try blindedMessage.withUnsafeBytes { blindedMessageBufferPtr in + try withUnsafeTemporaryAllocation(byteCount: blindedMessageBufferPtr.count, alignment: 1) { verificationBufferPtr in + let rsaPublicKey = CCryptoBoringSSL_EVP_PKEY_get0_RSA(self.pointer) + var outputCount = 0 + /// NOTE: BoringSSL promotes the use of `RSA_verify_raw` over `RSA_public_decrypt`. + guard CCryptoBoringSSL_RSA_verify_raw( + rsaPublicKey, + &outputCount, + verificationBufferPtr.baseAddress, + verificationBufferPtr.count, + signatureBufferPtr.baseAddress, + signatureBufferPtr.count, + RSA_NO_PADDING + ) == 1 else { + throw CryptoKitError.internalBoringSSLError() + } + guard + outputCount == blindedMessageBufferPtr.count, + memcmp(verificationBufferPtr.baseAddress!, blindedMessageBufferPtr.baseAddress!, blindedMessageBufferPtr.count) == 0 + else { + throw CryptoKitError(_RSA.BlindSigning.ProtocolError.signingFailure) + } + } + } + } + } + deinit { CCryptoBoringSSL_EVP_PKEY_free(self.pointer) } } } -#endif + +/// This namespace enum just provides helper functions for some of the steps outlined in the RFC. +enum BlindSigningHelpers { + fileprivate static func RSASSAPSSVerify( + rsaPublicKey: OpaquePointer!, + modulusByteCount: Int, + message: _RSA.BlindSigning.PreparedMessage, + signature: _RSA.Signing.RSASignature, + parameters: _RSA.BlindSigning.Parameters + ) throws -> Bool { + let hashDigestType = try DigestType(forDigestType: H.Digest.self) + return H.hash(data: message.rawRepresentation).withUnsafeBytes { messageHashBufferPtr in + withUnsafeTemporaryAllocation(byteCount: modulusByteCount, alignment: 1) { encodedMessageBufferPtr in + signature.withUnsafeBytes { signatureBufferPtr in + var outputCount = 0 + guard + /// NOTE: BoringSSL promotes the use of `RSA_verify_raw` over `RSA_public_decrypt`. + CCryptoBoringSSL_RSA_verify_raw( + rsaPublicKey, + &outputCount, + encodedMessageBufferPtr.baseAddress, + encodedMessageBufferPtr.count, + signatureBufferPtr.baseAddress, + signatureBufferPtr.count, + RSA_NO_PADDING + ) == 1, + outputCount == modulusByteCount, + CCryptoBoringSSL_RSA_verify_PKCS1_PSS_mgf1( + rsaPublicKey, + messageHashBufferPtr.baseAddress, + hashDigestType.dispatchTable, + hashDigestType.dispatchTable, + encodedMessageBufferPtr.baseAddress, + parameters.saltLength + ) == 1 + else { return false } + return true + } + } + } + } + + fileprivate static func EMSAPSSEncode( + rsaPublicKey: OpaquePointer!, + modulusByteCount: Int, + message: _RSA.BlindSigning.PreparedMessage, + parameters: _RSA.BlindSigning.Parameters + ) throws -> ArbitraryPrecisionInteger { + try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: modulusByteCount) { encodedMessageBufferPtr in + let hashDigestType = try DigestType(forDigestType: H.Digest.self) + guard H.hash(data: message.rawRepresentation).withUnsafeBytes({ hashBufferPtr in + CCryptoBoringSSL_RSA_padding_add_PKCS1_PSS_mgf1( + rsaPublicKey, + encodedMessageBufferPtr.baseAddress, + hashBufferPtr.baseAddress, + hashDigestType.dispatchTable, + hashDigestType.dispatchTable, + parameters.saltLength + ) + }) == 1 else { + switch ERR_GET_REASON(CCryptoBoringSSL_ERR_peek_last_error()) { + case RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE: + throw CryptoKitError(_RSA.BlindSigning.ProtocolError.messageTooLong) + default: + throw CryptoKitError.internalBoringSSLError() + } + } + return try ArbitraryPrecisionInteger(bytes: encodedMessageBufferPtr) + } + } +} diff --git a/Sources/_CryptoExtras/RSA/RSA_security.swift b/Sources/_CryptoExtras/RSA/RSA_security.swift index bfe60332..21b8c545 100644 --- a/Sources/_CryptoExtras/RSA/RSA_security.swift +++ b/Sources/_CryptoExtras/RSA/RSA_security.swift @@ -231,35 +231,26 @@ extension SecurityRSAPublicKey { extension SecKeyAlgorithm { fileprivate init(digestType: D.Type = D.self, padding: _RSA.Signing.Padding) throws { - switch digestType { - case is Insecure.SHA1.Digest.Type: - switch padding.backing { - case .pss: - self = .rsaSignatureDigestPSSSHA1 - case .pkcs1v1_5: - self = .rsaSignatureDigestPKCS1v15SHA1 - } - case is SHA256.Digest.Type: - switch padding.backing { - case .pss: - self = .rsaSignatureDigestPSSSHA256 - case .pkcs1v1_5: - self = .rsaSignatureDigestPKCS1v15SHA256 - } - case is SHA384.Digest.Type: - switch padding.backing { - case .pss: - self = .rsaSignatureDigestPSSSHA384 - case .pkcs1v1_5: - self = .rsaSignatureDigestPKCS1v15SHA384 - } - case is SHA512.Digest.Type: - switch padding.backing { - case .pss: - self = .rsaSignatureDigestPSSSHA512 - case .pkcs1v1_5: - self = .rsaSignatureDigestPKCS1v15SHA512 - } + switch (digestType, padding.backing) { + case (is Insecure.SHA1.Digest.Type, .pss): + self = .rsaSignatureDigestPSSSHA1 + case (is Insecure.SHA1.Digest.Type, .pkcs1v1_5): + self = .rsaSignatureDigestPKCS1v15SHA1 + case (is SHA256.Digest.Type, .pss): + self = .rsaSignatureDigestPSSSHA256 + case (is SHA256.Digest.Type, .pkcs1v1_5): + self = .rsaSignatureDigestPKCS1v15SHA256 + case (is SHA384.Digest.Type, .pss): + self = .rsaSignatureDigestPSSSHA384 + case (is SHA384.Digest.Type, .pkcs1v1_5): + self = .rsaSignatureDigestPKCS1v15SHA384 + case (is SHA512.Digest.Type, .pss): + self = .rsaSignatureDigestPSSSHA512 + case (is SHA512.Digest.Type, .pkcs1v1_5): + self = .rsaSignatureDigestPKCS1v15SHA512 + case (_, .pssZero): + // Explicitly unsupported: only used in RSABSSA, which is implemented using BoringSSL on all platforms. + throw CryptoKitError.incorrectParameterSize default: throw CryptoKitError.incorrectParameterSize } diff --git a/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift b/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift index e02b4cd1..3169d348 100644 --- a/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift +++ b/Sources/_CryptoExtras/Util/BoringSSLHelpers.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -// This is only used when bulding with BoringSSL. -#else +// NOTE: This file is unconditionally compiled because RSABSSA is implemented using BoringSSL on all platforms. import CCryptoBoringSSL +import CCryptoBoringSSLShims +import CryptoBoringWrapper import Foundation import Crypto @@ -93,4 +93,86 @@ extension FixedWidthInteger { return try block(&bn) } } -#endif + +extension _RSA.BlindSigning.PublicKey { + /// Construct a platform-specific RSA public key with the specified parameters. + /// + /// This constructor is used in tests in cases where test vectors provide the key information this way. + /// + /// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL + /// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation. + internal init(nHexString: String, eHexString: String, parameters: Parameters) throws { + let n = try ArbitraryPrecisionInteger(hexString: nHexString) + let e = try ArbitraryPrecisionInteger(hexString: eHexString) + + // Create BoringSSL RSA key. + guard let rsaPtr = n.withUnsafeBignumPointer({ n in + e.withUnsafeBignumPointer { e in + CCryptoBoringSSL_RSA_new_public_key(n, e) + } + }) else { throw CryptoKitError.internalBoringSSLError() } + defer { CCryptoBoringSSL_RSA_free(rsaPtr) } + + // Get PEM representation for key. + let pemRepresentation = BIOHelper.withWritableMemoryBIO { bio in + precondition(CCryptoBoringSSL_PEM_write_bio_RSAPublicKey(bio, rsaPtr) == 1) + return try! String(copyingUTF8MemoryBIO: bio) + } + + // Create a key (which might be backed by Security framework) from PEM representation. + try self.init(pemRepresentation: pemRepresentation, parameters: parameters) + } +} + + +extension _RSA.BlindSigning.PrivateKey { + /// Construct a platform-specific RSA private key with the specified parameters. + /// + /// This constructor is used in tests in cases where test vectors provide the key information this way. + /// + /// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL + /// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation. + internal init(nHexString: String, eHexString: String, dHexString: String, pHexString: String, qHexString: String, parameters: Parameters) throws { + let n = try ArbitraryPrecisionInteger(hexString: nHexString) + let e = try ArbitraryPrecisionInteger(hexString: eHexString) + let d = try ArbitraryPrecisionInteger(hexString: dHexString) + let p = try ArbitraryPrecisionInteger(hexString: pHexString) + let q = try ArbitraryPrecisionInteger(hexString: qHexString) + + // Compute the CRT params. + let dp = try FiniteFieldArithmeticContext(fieldSize: p - 1).residue(d) + let dq = try FiniteFieldArithmeticContext(fieldSize: q - 1).residue(d) + guard let qi = try FiniteFieldArithmeticContext(fieldSize: p).inverse(q) else { + throw CryptoKitError.internalBoringSSLError() + } + + // Create BoringSSL RSA key. + guard let rsaPtr = n.withUnsafeBignumPointer({ n in + e.withUnsafeBignumPointer { e in + d.withUnsafeBignumPointer { d in + p.withUnsafeBignumPointer { p in + q.withUnsafeBignumPointer { q in + dp.withUnsafeBignumPointer { dp in + dq.withUnsafeBignumPointer { dq in + qi.withUnsafeBignumPointer { qi in + CCryptoBoringSSL_RSA_new_private_key(n, e, d, p, q, dp, dq, qi) + } + } + } + } + } + } + } + }) else { throw CryptoKitError.internalBoringSSLError() } + defer { CCryptoBoringSSL_RSA_free(rsaPtr) } + + // Get PEM representation for key. + let pemRepresentation = BIOHelper.withWritableMemoryBIO { bio in + precondition(CCryptoBoringSSL_PEM_write_bio_RSAPrivateKey(bio, rsaPtr, nil, nil, 0, nil, nil) == 1) + return try! String(copyingUTF8MemoryBIO: bio) + } + + // Create a key (which might be backed by Security framework) from PEM representation. + try self.init(pemRepresentation: pemRepresentation, parameters: parameters) + } +} diff --git a/Sources/_CryptoExtras/Util/DigestType.swift b/Sources/_CryptoExtras/Util/DigestType.swift index e9e0d7f3..8434007d 100644 --- a/Sources/_CryptoExtras/Util/DigestType.swift +++ b/Sources/_CryptoExtras/Util/DigestType.swift @@ -11,9 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API -// Nothing to do in this case -#else + +// NOTE: This file is unconditionally compiled because RSABSSA is implemented using BoringSSL on all platforms. import CCryptoBoringSSL import Crypto @@ -55,4 +54,3 @@ struct DigestType { } } } -#endif diff --git a/Sources/_CryptoExtras/Util/RandomBytes.swift b/Sources/_CryptoExtras/Util/RandomBytes.swift index 1c68465f..87ff34dc 100644 --- a/Sources/_CryptoExtras/Util/RandomBytes.swift +++ b/Sources/_CryptoExtras/Util/RandomBytes.swift @@ -19,8 +19,12 @@ extension UnsafeMutableRawBufferPointer { return } - precondition(count <= self.count) + #if canImport(Darwin) || os(Linux) || os(Android) || os(Windows) var rng = SystemRandomNumberGenerator() + #else + fatalError("No secure random number generator on this platform.") + #endif + precondition(count <= self.count) // We store bytes 64-bits at a time until we can't anymore. var targetPtr = self @@ -39,3 +43,13 @@ extension UnsafeMutableRawBufferPointer { } } } + +extension SystemRandomNumberGenerator { + @inlinable + static func randomBytes(count: Int) -> [UInt8] { + Array(unsafeUninitializedCapacity: count) { buffer, initializedCount in + UnsafeMutableRawBufferPointer(start: buffer.baseAddress, count: buffer.count).initializeWithRandomBytes(count: count) + initializedCount = count + } + } +} diff --git a/Tests/CryptoBoringWrapperTests/ArbitraryPrecisionIntegerTests.swift b/Tests/CryptoBoringWrapperTests/ArbitraryPrecisionIntegerTests.swift index 3ea9a1c6..cc91a3db 100644 --- a/Tests/CryptoBoringWrapperTests/ArbitraryPrecisionIntegerTests.swift +++ b/Tests/CryptoBoringWrapperTests/ArbitraryPrecisionIntegerTests.swift @@ -109,4 +109,60 @@ final class ArbitraryPrecisionIntegerTests: XCTestCase { XCTAssertFalse(two > two) XCTAssertTrue(two >= two) } + + func testGCD() { + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+9, +13), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+9, -13), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-9, +13), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-9, -13), 1) + + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+13, +9), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+13, -9), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-13, +9), 1) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-13, -9), 1) + + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+9, +12), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+9, -12), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-9, +12), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-9, -12), 3) + + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+12, +9), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(+12, -9), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-12, +9), 3) + XCTAssertEqual(try ArbitraryPrecisionInteger.gcd(-12, -9), 3) + } + + func testIsCoprime() { + XCTAssert(try ArbitraryPrecisionInteger(+9).isCoprime(with: +13)) + XCTAssert(try ArbitraryPrecisionInteger(+9).isCoprime(with: -13)) + XCTAssert(try ArbitraryPrecisionInteger(-9).isCoprime(with: +13)) + XCTAssert(try ArbitraryPrecisionInteger(-9).isCoprime(with: -13)) + + XCTAssertFalse(try ArbitraryPrecisionInteger(+9).isCoprime(with: +27)) + XCTAssertFalse(try ArbitraryPrecisionInteger(+9).isCoprime(with: -27)) + XCTAssertFalse(try ArbitraryPrecisionInteger(-9).isCoprime(with: +27)) + XCTAssertFalse(try ArbitraryPrecisionInteger(-9).isCoprime(with: -27)) + } + + func testRandom() throws { + XCTAssertEqual(try ArbitraryPrecisionInteger.random(inclusiveMin: 4, exclusiveMax: 5), 4) + + var previousRandom = ArbitraryPrecisionInteger() + for _ in 1...1_000 { + let exclusiveMax = try ArbitraryPrecisionInteger(bytes: Data(repeating: UInt8.max, count: 2048/8)) + let random = try ArbitraryPrecisionInteger.random(inclusiveMin: 42, exclusiveMax: exclusiveMax) + XCTAssert(random >= ArbitraryPrecisionInteger(42)) + XCTAssert(random < exclusiveMax) + XCTAssert(random != previousRandom) + previousRandom = random + } + } + + func testDataRoundtrip() throws { + for value: Int64 in [0, 1, 42, 256, 1024, .max] { + let integer = ArbitraryPrecisionInteger(integerLiteral: value) + let bytes = try Data(bytesOf: integer, paddedToSize: (value.bitWidth + 7) / 8) + XCTAssertEqual(try ArbitraryPrecisionInteger(bytes: bytes), integer) + } + } } diff --git a/Tests/CryptoBoringWrapperTests/FiniteFieldArithmeticTests.swift b/Tests/CryptoBoringWrapperTests/FiniteFieldArithmeticTests.swift new file mode 100644 index 00000000..5f6d1ce0 --- /dev/null +++ b/Tests/CryptoBoringWrapperTests/FiniteFieldArithmeticTests.swift @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.md for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +@testable import CryptoBoringWrapper +import XCTest + + +final class FiniteFieldArithmeticTests: XCTestCase { + func testResidue() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.residue(-4), 2) + XCTAssertEqual(try ff.residue(-3), 0) + XCTAssertEqual(try ff.residue(-2), 1) + XCTAssertEqual(try ff.residue(-1), 2) + XCTAssertEqual(try ff.residue(+0), 0) + XCTAssertEqual(try ff.residue(+1), 1) + XCTAssertEqual(try ff.residue(+2), 2) + XCTAssertEqual(try ff.residue(+3), 0) + XCTAssertEqual(try ff.residue(+4), 1) + XCTAssertEqual(try ff.residue(+5), 2) + XCTAssertEqual(try ff.residue(+6), 0) + } + + func testSquare() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.square(1), 1) + XCTAssertEqual(try ff.square(2), 1) + XCTAssertEqual(try ff.square(3), 0) + XCTAssertEqual(try ff.square(4), 1) + XCTAssertEqual(try ff.square(5), 1) + XCTAssertEqual(try ff.square(-5), 1) + } + + func testMultiply() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.multiply(1, 1), 1) + XCTAssertEqual(try ff.multiply(2, 3), 0) + XCTAssertEqual(try ff.multiply(4, 2), 2) + XCTAssertEqual(try ff.multiply(4, -2), 1) + XCTAssertEqual(try ff.multiply(-4, 2), 1) + XCTAssertEqual(try ff.multiply(-4, -2), 2) + } + + func testAdd() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.add(1, 0), 1) + XCTAssertEqual(try ff.add(1, 1), 2) + XCTAssertEqual(try ff.add(1, 2), 0) + XCTAssertEqual(try ff.add(1, 3), 1) + XCTAssertEqual(try ff.add(-1, 3), 2) + } + + func testSubtract() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.subtract(0, from: 1), 1) + XCTAssertEqual(try ff.subtract(1, from: 1), 0) + XCTAssertEqual(try ff.subtract(2, from: 1), 2) + XCTAssertEqual(try ff.subtract(5, from: 22), 2) + } + + func testPositiveSquareRoot() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.positiveSquareRoot(1), 1) + XCTAssertEqual(try ff.positiveSquareRoot(4), 1) + XCTAssertEqual(try ff.positiveSquareRoot(9), 0) + XCTAssertEqual(try ff.positiveSquareRoot(16), 1) + XCTAssertEqual(try ff.positiveSquareRoot(25), 1) + } + + func testInverse() throws { + let ff = try FiniteFieldArithmeticContext(fieldSize: 3) + XCTAssertEqual(try ff.inverse(1), 1) + XCTAssertEqual(try ff.inverse(2), 2) + XCTAssertEqual(try ff.inverse(3), nil) + XCTAssertEqual(try ff.inverse(4), 1) + XCTAssertEqual(try ff.inverse(5), 2) + XCTAssertEqual(try ff.inverse(6), nil) + for i: Int64 in 1...100 { + let integer = ArbitraryPrecisionInteger(integerLiteral: i) + let inverse = try ff.inverse(integer) + if i % 3 == 0 { + XCTAssertNil(inverse) + } else { + XCTAssertEqual(try ff.multiply(integer, XCTUnwrap(inverse)), 1) + } + } + } + + func testPow() throws { + let m: ArbitraryPrecisionInteger = 7 + let ff = try FiniteFieldArithmeticContext(fieldSize: m) + for (x, p, expectedResult): (ArbitraryPrecisionInteger, ArbitraryPrecisionInteger, ArbitraryPrecisionInteger) in [ + (1, 0, 1), (1, 1, 1), (1, 2, 1), (1, 3, 1), + (2, 0, 1), (2, 1, 2), (2, 2, 4), (2, 3, 1), + (3, 0, 1), (3, 1, 3), (3, 2, 2), (3, 3, 6), + (5, 0, 1), (5, 1, 5), (5, 2, 4), (5, 3, 6), + (7, 0, 1), (7, 1, 0), (7, 2, 0), (7, 3, 0), // x = m + (8, 0, 1), (8, 1, 1), (8, 2, 1), (8, 3, 1), // x > m + ] { + let message = "\(x)^\(p) (mod \(m))" + XCTAssertEqual(try ff.pow(x, p), expectedResult, message) + if x < m { + XCTAssertEqual(try ff.pow(secret: x, p), expectedResult, message) + XCTAssertEqual(try ff.pow(secret: x, secret: p), expectedResult, message) + } else { + XCTAssertThrowsError(try ff.pow(secret: x, p), message) { error in + switch (error as? CryptoBoringWrapperError) { + case .incorrectParameterSize: break // OK + default: XCTFail("Unexpected error: \(error)") + } + } + } + } + } +} diff --git a/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift b/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift new file mode 100644 index 00000000..2cf20c87 --- /dev/null +++ b/Tests/_CryptoExtrasTests/TestRSABlindSigning.swift @@ -0,0 +1,167 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.md for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import XCTest +import Crypto +@testable import _CryptoExtras + +fileprivate struct TestVector: Codable { + var name, p, q, n, e, d, msg, msg_prefix, prepared_msg, salt, inv, blinded_msg, blind_sig, sig: String + + var parameters: _RSA.BlindSigning.Parameters { + let messagePaddingByteCount = (self.prepared_msg.count - self.msg.count) / 2 + let saltByteCount = self.salt.count / 2 + switch (saltByteCount, messagePaddingByteCount) { + case (0, 0): + return .RSABSSA_SHA384_PSSZERO_Deterministic + case (0, 32): + return .RSABSSA_SHA384_PSSZERO_Randomized + case (SHA384.byteCount, 0): + return .RSABSSA_SHA384_PSS_Deterministic + case (SHA384.byteCount, 32): + return .RSABSSA_SHA384_PSS_Randomized + default: + fatalError("Unsupported test vector; salt length: \(saltByteCount); message padding: \(messagePaddingByteCount).") + } + } + + static func load(from fileURL: URL) throws -> [Self] { + let json = try Data(contentsOf: fileURL) + let decoder = JSONDecoder() + return try decoder.decode([Self].self, from: json) + } +} + +final class TestRSABlindSigning: XCTestCase { + func testAgainstRFC9474TestVectors() throws { + let testVectors = try TestVector.load(from: URL( + fileURLWithPath: "../_CryptoExtrasVectors/rfc9474.json", + relativeTo: URL(fileURLWithPath: #file) + )) + + for testVector in testVectors { + // Prepare + do { + let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e, parameters: testVector.parameters) + let message = try Data(hexString: testVector.msg) + let preparedMessage = publicKey.prepare(message) + switch testVector.parameters.preparation { + case .identity: + XCTAssertEqual(preparedMessage.rawRepresentation, message) + case .randomized: + XCTAssertEqual(preparedMessage.rawRepresentation.dropFirst(32), message) + } + } + + // Blind + do { + let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e, parameters: testVector.parameters) + let preparedMessage = try _RSA.BlindSigning.PreparedMessage(rawRepresentation: Data(hexString: testVector.prepared_msg)) + let blindingResult = try publicKey.blind(preparedMessage) + // NOTE: Sadly we can't validate the blinded message against the test vectors because BoringSSL doesn't + // have the APIs we would need to specify a fixed salt value. + XCTAssertEqual(blindingResult.blindedMessage.hexString.count, testVector.blinded_msg.count) + XCTAssertEqual(blindingResult.inverse.rawRepresentation.hexString.count, testVector.inv.count) + } + + // BlindSign + do { + let privateKey = try _RSA.BlindSigning.PrivateKey( + nHexString: testVector.n, + eHexString: testVector.e, + dHexString: testVector.d, + pHexString: testVector.p, + qHexString: testVector.q, + parameters: testVector.parameters + ) + let blindedMessage = try Data(hexString: testVector.blinded_msg) + let blindSignature = try privateKey.blindSignature(for: blindedMessage) + XCTAssertEqual( + blindSignature.rawRepresentation.hexString, + try Data(hexString: testVector.blind_sig).hexString + ) + } + + // Finalize + do { + let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e, parameters: testVector.parameters) + let blindSignature = try _RSA.BlindSigning.BlindSignature(rawRepresentation: Data(hexString: testVector.blind_sig)) + let preparedMessage = try _RSA.BlindSigning.PreparedMessage(rawRepresentation: Data(hexString: testVector.prepared_msg)) + let blindingInverse = try _RSA.BlindSigning.BlindingInverse(rawRepresentation: Data(hexString: testVector.inv)) + let signature = try publicKey.finalize(blindSignature, for: preparedMessage, blindingInverse: blindingInverse) + XCTAssertEqual( + signature.rawRepresentation.hexString, + try Data(hexString: testVector.sig).hexString + ) + } + + // Verification + do { + let publicKey = try _RSA.BlindSigning.PublicKey(nHexString: testVector.n, eHexString: testVector.e, parameters: testVector.parameters) + let signature = try _RSA.Signing.RSASignature(rawRepresentation: Data(hexString: testVector.sig)) + let preparedMessage = try _RSA.BlindSigning.PreparedMessage(rawRepresentation: Data(hexString: testVector.prepared_msg)) + XCTAssert(publicKey.isValidSignature(signature, for: preparedMessage)) + } + } + } + + func testBlindSign_messageTooLargeForKeyModulus_throwsIncorrectParameterSize() throws { + let privateKeyPEM = """ + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEAnuCNx4shvbE2puRC0DiTGVnQHN3BaUm2g6Db1DqoFndFUcxP + ZpxkMsr+zq5rB/sJlfpYvK3INZYKNSXeCtt3sJYmABWutI/MIuPsHZVdG98Cp3Mr + 7Zt+XOIE6zr9nK7yDN1KpQHNUE4iRfaAMRFy2r4r+va8M8KYbw3fTZmnk0SFjS0l + 4x3JmFbmjqTKWGEXARmAX6niA/c7Ruhq+hvnG9zkOnPzr6z3R6Eo0whXYoC+Ue3f + maHmB7IfpkDLBPy9xdCvX6Hx6Q/sDC9nAOBdfo4c0Zy7gb61Lv5CAT0DPC8KA7VG + 0QJY+ozqsrvrXMV5FNnAP+TtqSE0ENaESY3bvwIDAQABAoIBABd2Nbm39B2dI+L1 + ZmB1WlOuDauVm9A+kHNTt+LZXqUiq9vuffM7ORi/HW3MGAYvrjS1qZEDYMN5Crza + gLW9vykWoznz+b60VYL5gY7E1eEdx7iOq3gFNF3nCq7qITWYKCp6K2G+qGEpiKoo + Qrn8R6fB2aDP+u7x16zesZE2FacLhc/N4uzkJsus6on+y0QfM5w5qlhFISeWq+lp + FimZSw6BdvbpK+iSm5zY03NYLSPdllmTJ+FV+Yc5YXdaqrz9p+v0EEnkUwQm3Sm+ + shEgzXeeZQgE/g9zcWuaAnz5hkcGUzb43QBbbh0E4tGFdSOF3D+z090Pe6eMwAyp + ldlBR7ECgYEAzvFvMDzfaOtTVemMy+hgRm/N1WrEYBl1XxHiTgkjaNEjw9YiiorL + klAK5nurE1RqwclqE+HtPKr44nZLYFw2Vlqpfj1tk/Ln4TPQukZrZvfQNJFZYjym + ATgw3i1bwVfJ5rdqY8wBjfBVjrCacO8eoZ1lB2fqb9xgW59/+w/cdnUCgYEAxIov + oT9sdwF0nTY5xA+MOPYYjEY5HDdu/KKB+K8sUkgQbmoiQIVc3cKec4l5SrhB1ncE + UBGyyX7tmF+fBdWS550zP12m4Qs3Fce1foxa3aaKXK4/sj3qLYI31FPM2z6vdgE4 + bJbYu5SITlkz3wZbkOeph/2BHKRFfDHPg0cnSuMCgYBylour2TkX/p5RhxYYXp7Y + wdXm48zDLbWpI9z8uuCpjIzSRsMvlbUtWjb+8uGCvY6zqVScl9BmdIGF3FzWiZjo + 7iDGLzt63dj6AVgFnTKhfH0Ebqtg0xZUvImKrPEOuQ6qO0uk4PTHZJnrfey2tiFu + +hlUJX1R3WRZt5MFMP4xdQKBgEwHSLWP212N6paGS4JUoWHHkWdyItWPfBeupaiV + 2wdZaUHNPMLI1EvU5Ya8P3dwH8fe8oQm1Iqt1yuCkfmnzNRcM17n045q0DxUrRjv + IpdrvUps/abt3JEONpqkcDK/5RA5GKKpF944byIfz7kOtI0xkJtSrYdu5JJOkn+u + Hr0RAoGAGSlR9rAEbWRaNx2PJWmyb/A5LVWU85SKCnBl13v4OqDZJ+3akvYxn8WK + cWbG8agtNNiqR4Rt+ehSZGwRwT6ZXL5BSj0UHJTZk1GrzMz4+rAo238UnDbgEExn + UeXjD0eUYZEtiLapKsXqTtaxmUfPT8vQ9v1GKJfTgTLX+HVdRGg= + -----END RSA PRIVATE KEY----- + """ + let blindedMessageHexString = """ + e949d41ab280c8b179345477ae32d17364e18961303b75669d426f435abecb91\ + aab63487311197cb7c204b87408a5e39b04e04e1b3dcab49d691dbcdf578ed04\ + dc7362f1b56b2d39f43708dcb33fabc569bc21fd5c1156e042c366b39771e391\ + 12082231f9abdcb3ebd3b98d25f66b42147774aad1c4f1d59d11e519dd1a1925\ + 37af580dcc28431902044d86815db8d8643df4beb337255cb4563e4c1d84c011\ + 30170f645956be2e1945396327c666ddb10772645c2ae4de8c9c4912c9f7c14d\ + d42b43aa98cef406d45c1df3035e115e2a878766624c9d31488518e2667987fd\ + 2950c126425538ad676e23a26c0f3e0523a307c557e3a6471771a5635b704c56 + """ + let privateKey = try _RSA.BlindSigning.PrivateKey(pemRepresentation: privateKeyPEM) + let blindedMessage = try Data(hexString: blindedMessageHexString) + XCTAssertThrowsError(try privateKey.blindSignature(for: blindedMessage)) { error in + guard let error = error as? CryptoKitError, case .incorrectParameterSize = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } +} diff --git a/Tests/_CryptoExtrasTests/TestRSABlindSigningAPI.swift b/Tests/_CryptoExtrasTests/TestRSABlindSigningAPI.swift new file mode 100644 index 00000000..c131ad90 --- /dev/null +++ b/Tests/_CryptoExtrasTests/TestRSABlindSigningAPI.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCrypto open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.md for the list of SwiftCrypto project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import XCTest +import Crypto +import _CryptoExtras // NOTE: No @testable import, because we want to test the public API. + +final class TestRSABlindSigningAPI: XCTestCase { + func testEndToEnd() throws { + let allNamedRFC9474Variants: [_RSA.BlindSigning.Parameters] = [ + .RSABSSA_SHA384_PSSZERO_Deterministic, + .RSABSSA_SHA384_PSSZERO_Randomized, + .RSABSSA_SHA384_PSS_Deterministic, + .RSABSSA_SHA384_PSS_Randomized, + ] + let keySizes: [_RSA.Signing.KeySize] = [ + .bits2048, + .bits3072, + .bits4096, + ] + for parameters in allNamedRFC9474Variants { + for keySize in keySizes { + // [Issuer] Create key-pair (other initializers are available). + let privateKey = try _RSA.BlindSigning.PrivateKey(keySize: keySize, parameters: parameters) + + // [Client] Create public key (other initializers are available). + let publicKey = privateKey.publicKey + + // [Client] Have a message they wish to use. + let message = Data("This is some input data".utf8) + + // [Client] Prepare the message. + let preparedMessage = publicKey.prepare(message) + + // [Client] Blind the message to send to the server and get its blinding inverse. + let blindingResult = try publicKey.blind(preparedMessage) + + // [Issuer] Blind sign, construting the blinded message from the bytes received from the client. + let blindSignature = try privateKey.blindSignature(for: blindingResult.blindedMessage) + + // [Client] Finalize using the blind inverse to unblind the signature. + let unblindedSignature = try publicKey.finalize(blindSignature, for: preparedMessage, blindingInverse: blindingResult.inverse) + + // [Verifier] Verify the unblinded signature. + XCTAssert(publicKey.isValidSignature(unblindedSignature, for: preparedMessage)) + } + } + } +} diff --git a/Tests/_CryptoExtrasVectors/rfc9474.json b/Tests/_CryptoExtrasVectors/rfc9474.json new file mode 100644 index 00000000..bb7f864b --- /dev/null +++ b/Tests/_CryptoExtrasVectors/rfc9474.json @@ -0,0 +1,69 @@ +[ + { + "name": "RSABSSA-SHA384-PSS-Randomized", + "p": "e1f4d7a34802e27c7392a3cea32a262a34dc3691bd87f3f310dc75673488930559c120fd0410194fb8a0da55bd0b81227e843fdca6692ae80e5a5d414116d4803fca7d8c30eaaae57e44a1816ebb5c5b0606c536246c7f11985d731684150b63c9a3ad9e41b04c0b5b27cb188a692c84696b742a80d3cd00ab891f2457443dadfeba6d6daf108602be26d7071803c67105a5426838e6889d77e8474b29244cefaf418e381b312048b457d73419213063c60ee7b0d81820165864fef93523c9635c22210956e53a8d96322493ffc58d845368e2416e078e5bcb5d2fd68ae6acfa54f9627c42e84a9d3f2774017e32ebca06308a12ecc290c7cd1156dcccfb2311", + "q": "c601a9caea66dc3835827b539db9df6f6f5ae77244692780cd334a006ab353c806426b60718c05245650821d39445d3ab591ed10a7339f15d83fe13f6a3dfb20b9452c6a9b42eaa62a68c970df3cadb2139f804ad8223d56108dfde30ba7d367e9b0a7a80c4fdba2fd9dde6661fc73fc2947569d2029f2870fc02d8325acf28c9afa19ecf962daa7916e21afad09eb62fe9f1cf91b77dc879b7974b490d3ebd2e95426057f35d0a3c9f45f79ac727ab81a519a8b9285932d9b2e5ccd347e59f3f32ad9ca359115e7da008ab7406707bd0e8e185a5ed8758b5ba266e8828f8d863ae133846304a2936ad7bc7c9803879d2fc4a28e69291d73dbd799f8bc238385", + "n": "aec4d69addc70b990ea66a5e70603b6fee27aafebd08f2d94cbe1250c556e047a928d635c3f45ee9b66d1bc628a03bac9b7c3f416fe20dabea8f3d7b4bbf7f963be335d2328d67e6c13ee4a8f955e05a3283720d3e1f139c38e43e0338ad058a9495c53377fc35be64d208f89b4aa721bf7f7d3fef837be2a80e0f8adf0bcd1eec5bb040443a2b2792fdca522a7472aed74f31a1ebe1eebc1f408660a0543dfe2a850f106a617ec6685573702eaaa21a5640a5dcaf9b74e397fa3af18a2f1b7c03ba91a6336158de420d63188ee143866ee415735d155b7c2d854d795b7bc236cffd71542df34234221a0413e142d8c61355cc44d45bda94204974557ac2704cd8b593f035a5724b1adf442e78c542cd4414fce6f1298182fb6d8e53cef1adfd2e90e1e4deec52999bdc6c29144e8d52a125232c8c6d75c706ea3cc06841c7bda33568c63a6c03817f722b50fcf898237d788a4400869e44d90a3020923dc646388abcc914315215fcd1bae11b1c751fd52443aac8f601087d8d42737c18a3fa11ecd4131ecae017ae0a14acfc4ef85b83c19fed33cfd1cd629da2c4c09e222b398e18d822f77bb378dea3cb360b605e5aa58b20edc29d000a66bd177c682a17e7eb12a63ef7c2e4183e0d898f3d6bf567ba8ae84f84f1d23bf8b8e261c3729e2fa6d07b832e07cddd1d14f55325c6f924267957121902dc19b3b32948bdead5", + "e": "010001", + "d": "0d43242aefe1fb2c13fbc66e20b678c4336d20b1808c558b6e62ad16a287077180b177e1f01b12f9c6cd6c52630257ccef26a45135a990928773f3bd2fc01a313f1dac97a51cec71cb1fd7efc7adffdeb05f1fb04812c924ed7f4a8269925dad88bd7dcfbc4ef01020ebfc60cb3e04c54f981fdbd273e69a8a58b8ceb7c2d83fbcbd6f784d052201b88a9848186f2a45c0d2826870733e6fd9aa46983e0a6e82e35ca20a439c5ee7b502a9062e1066493bdadf8b49eb30d9558ed85abc7afb29b3c9bc644199654a4676681af4babcea4e6f71fe4565c9c1b85d9985b84ec1abf1a820a9bbebee0df1398aae2c85ab580a9f13e7743afd3108eb32100b870648fa6bc17e8abac4d3c99246b1f0ea9f7f93a5dd5458c56d9f3f81ff2216b3c3680a13591673c43194d8e6fc93fc1e37ce2986bd628ac48088bc723d8fbe293861ca7a9f4a73e9fa63b1b6d0074f5dea2a624c5249ff3ad811b6255b299d6bc5451ba7477f19c5a0db690c3e6476398b1483d10314afd38bbaf6e2fbdbcd62c3ca9797a420ca6034ec0a83360a3ee2adf4b9d4ba29731d131b099a38d6a23cc463db754603211260e99d19affc902c915d7854554aabf608e3ac52c19b8aa26ae042249b17b2d29669b5c859103ee53ef9bdc73ba3c6b537d5c34b6d8f034671d7f3a8a6966cc4543df223565343154140fd7391c7e7be03e241f4ecfeb877a051", + "msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "msg_prefix": "8417e699b219d583fb6216ae0c53ca0e9723442d02f1d1a34295527e7d929e8b", + "prepared_msg": "8417e699b219d583fb6216ae0c53ca0e9723442d02f1d1a34295527e7d929e8b8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "salt": "051722b35f458781397c3a671a7d3bd3096503940e4c4f1aaa269d60300ce449555cd7340100df9d46944c5356825abf", + "inv": "80682c48982407b489d53d1261b19ec8627d02b8cda5336750b8cee332ae260de57b02d72609c1e0e9f28e2040fc65b6f02d56dbd6aa9af8fde656f70495dfb723ba01173d4707a12fddac628ca29f3e32340bd8f7ddb557cf819f6b01e445ad96f874ba235584ee71f6581f62d4f43bf03f910f6510deb85e8ef06c7f09d9794a008be7ff2529f0ebb69decef646387dc767b74939265fec0223aa6d84d2a8a1cc912d5ca25b4e144ab8f6ba054b54910176d5737a2cff011da431bd5f2a0d2d66b9e70b39f4b050e45c0d9c16f02deda9ddf2d00f3e4b01037d7029cd49c2d46a8e1fc2c0c17520af1f4b5e25ba396afc4cd60c494a4c426448b35b49635b337cfb08e7c22a39b256dd032c00adddafb51a627f99a0e1704170ac1f1912e49d9db10ec04c19c58f420212973e0cb329524223a6aa56c7937c5dffdb5d966b6cd4cbc26f3201dd25c80960a1a111b32947bb78973d269fac7f5186530930ed19f68507540eed9e1bab8b00f00d8ca09b3f099aae46180e04e3584bd7ca054df18a1504b89d1d1675d0966c4ae1407be325cdf623cf13ff13e4a28b594d59e3eadbadf6136eee7a59d6a444c9eb4e2198e8a974f27a39eb63af2c9af3870488b8adaad444674f512133ad80b9220e09158521614f1faadfe8505ef57b7df6813048603f0dd04f4280177a11380fbfc861dbcbd7418d62155248dad5fdec0991f", + "blinded_msg": "aa3ee045138d874669685ffaef962c7694a9450aa9b4fd6465db9b3b75a522bb921c4c0fdcdfae9667593255099cff51f5d3fd65e8ffb9d3b3036252a6b51b6edfb3f40382b2bbf34c0055e4cbcc422850e586d84f190cd449af11dc65545f5fe26fd89796eb87da4bda0c545f397cddfeeb56f06e28135ec74fd477949e7677f6f36cfae8fd5c1c5898b03b9c244cf6d1a4fb7ad1cb43aff5e80cb462fac541e72f67f0a50f1843d1759edfaae92d1a916d3f0efaf4d650db416c3bf8abdb5414a78cebc97de676723cb119e77aea489f2bbf530c440ebc5a75dccd3ebf5a412a5f346badd61bee588e5917bdcce9dc33c882e39826951b0b8276c6203971947072b726e935816056ff5cb11a71ca2946478584126bb877acdf87255f26e6cca4e0878801307485d3b7bb89b289551a8b65a7a6b93db010423d1406e149c87731910306e5e410b41d4da3234624e74f92845183e323cf7eb244f212a695f8856c675fbc3a021ce649e22c6f0d053a9d238841cf3afdc2739f99672a419ae13c17f1f8a3bc302ec2e7b98e8c353898b7150ad8877ec841ea6e4b288064c254fefd0d049c3ad196bf7ffa535e74585d0120ce728036ed500942fbd5e6332c298f1ffebe9ff60c1e117b274cf0cb9d70c36ee4891528996ec1ed0b178e9f3c0c0e6120885f39e8ccaadbb20f3196378c07b1ff22d10049d3039a7a92fe7efdd95d", + "blind_sig": "3f4a79eacd4445fca628a310d41e12fcd813c4d43aa4ef2b81226953248d6d00adfee6b79cb88bfa1f99270369fd063c023e5ed546719b0b2d143dd1bca46b0e0e615fe5c63d95c5a6b873b8b50bc52487354e69c3dfbf416e7aca18d5842c89b676efdd38087008fa5a810161fcdec26f20ccf2f1e6ab0f9d2bb93e051cb9e86a9b28c5bb62fd5f5391379f887c0f706a08bcc3b9e7506aaf02485d688198f5e22eefdf837b2dd919320b17482c5cc54271b4ccb41d267629b3f844fd63750b01f5276c79e33718bb561a152acb2eb36d8be75bce05c9d1b94eb609106f38226fb2e0f5cd5c5c39c59dda166862de498b8d92f6bcb41af433d65a2ac23da87f39764cb64e79e74a8f4ce4dd567480d967cefac46b6e9c06434c3715635834357edd2ce6f105eea854ac126ccfa3de2aac5607565a4e5efaac5eed491c335f6fc97e6eb7e9cea3e12de38dfb315220c0a3f84536abb2fdd722813e083feda010391ac3d8fd1cd9212b5d94e634e69ebcc800c4d5c4c1091c64afc37acf563c7fc0a6e4c082bc55544f50a7971f3fb97d5853d72c3af34ffd5ce123998be5360d1059820c66a81e1ee6d9c1803b5b62af6bc877526df255b6d1d835d8c840bebbcd6cc0ee910f17da37caf8488afbc08397a1941fcc79e76a5888a95b3d5405e13f737bea5c78d716a48eb9dc0aec8de39c4b45c6914ad4a8185969f70b1adf46", + "sig": "191e941c57510e22d29afad257de5ca436d2316221fe870c7cb75205a6c071c2735aed0bc24c37f3d5bd960ab97a829a508f966bbaed7a82645e65eadaf24ab5e6d9421392c5b15b7f9b640d34fec512846a3100b80f75ef51064602118c1a77d28d938f6efc22041d60159a518d3de7c4d840c9c68109672d743d299d8d2577ef60c19ab463c716b3fa75fa56f5735349d414a44df12bf0dd44aa3e10822a651ed4cb0eb6f47c9bd0ef14a034a7ac2451e30434d513eb22e68b7587a8de9b4e63a059d05c8b22c7c51e2cfee2d8bef511412e93c859a13726d87c57d1bc4c2e68ab121562f839c3a3d233e87ed63c69b7e57525367753fbebcc2a9805a2802659f5888b2c69115bf865559f10d906c09d048a0d71bfee4b33857393ec2b69e451433496d02c9a7910abb954317720bbde9e69108eafc3e90bad3d5ca4066d7b1e49013fa04e948104a1dd82b12509ecb146e948c54bd8bfb5e6d18127cd1f7a93c3cf9f2d869d5a78878c03fe808a0d799e910be6f26d18db61c485b303631d3568368fc41986d08a95ea6ac0592240c19d7b22416b9c82ae6241e211dd5610d0baaa9823158f9c32b66318f5529491b7eeadcaa71898a63bac9d95f4aa548d5e97568d744fc429104e32edd9c87519892a198a30d333d427739ffb9607b092e910ae37771abf2adb9f63bc058bf58062ad456cb934679795bbdfcdfad5e0f2" + }, + { + "name": "RSABSSA-SHA384-PSSZERO-Randomized", + "p": "e1f4d7a34802e27c7392a3cea32a262a34dc3691bd87f3f310dc75673488930559c120fd0410194fb8a0da55bd0b81227e843fdca6692ae80e5a5d414116d4803fca7d8c30eaaae57e44a1816ebb5c5b0606c536246c7f11985d731684150b63c9a3ad9e41b04c0b5b27cb188a692c84696b742a80d3cd00ab891f2457443dadfeba6d6daf108602be26d7071803c67105a5426838e6889d77e8474b29244cefaf418e381b312048b457d73419213063c60ee7b0d81820165864fef93523c9635c22210956e53a8d96322493ffc58d845368e2416e078e5bcb5d2fd68ae6acfa54f9627c42e84a9d3f2774017e32ebca06308a12ecc290c7cd1156dcccfb2311", + "q": "c601a9caea66dc3835827b539db9df6f6f5ae77244692780cd334a006ab353c806426b60718c05245650821d39445d3ab591ed10a7339f15d83fe13f6a3dfb20b9452c6a9b42eaa62a68c970df3cadb2139f804ad8223d56108dfde30ba7d367e9b0a7a80c4fdba2fd9dde6661fc73fc2947569d2029f2870fc02d8325acf28c9afa19ecf962daa7916e21afad09eb62fe9f1cf91b77dc879b7974b490d3ebd2e95426057f35d0a3c9f45f79ac727ab81a519a8b9285932d9b2e5ccd347e59f3f32ad9ca359115e7da008ab7406707bd0e8e185a5ed8758b5ba266e8828f8d863ae133846304a2936ad7bc7c9803879d2fc4a28e69291d73dbd799f8bc238385", + "n": "aec4d69addc70b990ea66a5e70603b6fee27aafebd08f2d94cbe1250c556e047a928d635c3f45ee9b66d1bc628a03bac9b7c3f416fe20dabea8f3d7b4bbf7f963be335d2328d67e6c13ee4a8f955e05a3283720d3e1f139c38e43e0338ad058a9495c53377fc35be64d208f89b4aa721bf7f7d3fef837be2a80e0f8adf0bcd1eec5bb040443a2b2792fdca522a7472aed74f31a1ebe1eebc1f408660a0543dfe2a850f106a617ec6685573702eaaa21a5640a5dcaf9b74e397fa3af18a2f1b7c03ba91a6336158de420d63188ee143866ee415735d155b7c2d854d795b7bc236cffd71542df34234221a0413e142d8c61355cc44d45bda94204974557ac2704cd8b593f035a5724b1adf442e78c542cd4414fce6f1298182fb6d8e53cef1adfd2e90e1e4deec52999bdc6c29144e8d52a125232c8c6d75c706ea3cc06841c7bda33568c63a6c03817f722b50fcf898237d788a4400869e44d90a3020923dc646388abcc914315215fcd1bae11b1c751fd52443aac8f601087d8d42737c18a3fa11ecd4131ecae017ae0a14acfc4ef85b83c19fed33cfd1cd629da2c4c09e222b398e18d822f77bb378dea3cb360b605e5aa58b20edc29d000a66bd177c682a17e7eb12a63ef7c2e4183e0d898f3d6bf567ba8ae84f84f1d23bf8b8e261c3729e2fa6d07b832e07cddd1d14f55325c6f924267957121902dc19b3b32948bdead5", + "e": "010001", + "d": "0d43242aefe1fb2c13fbc66e20b678c4336d20b1808c558b6e62ad16a287077180b177e1f01b12f9c6cd6c52630257ccef26a45135a990928773f3bd2fc01a313f1dac97a51cec71cb1fd7efc7adffdeb05f1fb04812c924ed7f4a8269925dad88bd7dcfbc4ef01020ebfc60cb3e04c54f981fdbd273e69a8a58b8ceb7c2d83fbcbd6f784d052201b88a9848186f2a45c0d2826870733e6fd9aa46983e0a6e82e35ca20a439c5ee7b502a9062e1066493bdadf8b49eb30d9558ed85abc7afb29b3c9bc644199654a4676681af4babcea4e6f71fe4565c9c1b85d9985b84ec1abf1a820a9bbebee0df1398aae2c85ab580a9f13e7743afd3108eb32100b870648fa6bc17e8abac4d3c99246b1f0ea9f7f93a5dd5458c56d9f3f81ff2216b3c3680a13591673c43194d8e6fc93fc1e37ce2986bd628ac48088bc723d8fbe293861ca7a9f4a73e9fa63b1b6d0074f5dea2a624c5249ff3ad811b6255b299d6bc5451ba7477f19c5a0db690c3e6476398b1483d10314afd38bbaf6e2fbdbcd62c3ca9797a420ca6034ec0a83360a3ee2adf4b9d4ba29731d131b099a38d6a23cc463db754603211260e99d19affc902c915d7854554aabf608e3ac52c19b8aa26ae042249b17b2d29669b5c859103ee53ef9bdc73ba3c6b537d5c34b6d8f034671d7f3a8a6966cc4543df223565343154140fd7391c7e7be03e241f4ecfeb877a051", + "msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "msg_prefix": "84ea86c8cf3beedfed73beceabd792027c609d1100bf041fdd60d826a718130d", + "prepared_msg": "84ea86c8cf3beedfed73beceabd792027c609d1100bf041fdd60d826a718130d8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "salt": "", + "encoded_msg": "37f4ea66054b3570f2c46f43125a8df8d751a81db1003edcc70e9888cb3d0fa71bb7634437a779c1bf9e84e88b3479894490ee41cd69fc8e911478326fe8460d1699f96abedde22ba0ba25a02f78bae77eb039decd41e6cd40fecc28f301c94d5644eb3e55b316569e2bec3ccf8e33b06eb6defca5fe672613d33ea60f84daa560ded4c1c5e65613fb19e090d0fc96a1394e29dfad6a7644362bf30bdc90c7ca0a065190f5a099b5c33ae787b872518a724d9aa139229656eb21053bbe86c38f6d03b4c6fa37a900935d9b8d19e0c394be4af6af028680996e3fd533b6698ce9e2ed6a9f96d4d3a682027ae5240040e55d75017dc303b7142c1f7e17b79778a94431398d21dc0cc7ae454cc0d6cf4db4d588d3fd15fd7f71576052fd2a52d688f99790dfb13808ecb24b6b9e9a43a8c0105670ec3ad8d6318a9c6a9cef9eb99b36d74b8e83dbacf6e8100e135b609850b34a4b01091b263678d7cd9905af2ffda801a2888d863a25211903b43cb5e59f5dba6bc18713ce4f028f1774c593664912f1d181d4544a13a1da354332d8595f59cf5af260a8aaf21a6bc948b5d5d4a520c1f72c216259dc12a33c2a3bd4d32ff2bf3de2ffe76e51f8af030b40fadc5899e740da20be1dd5a50f701292ceaee51fa35d9a047f3efc6543dc583fb3f23abeade39c2a5b5b352de26d7a11267435be7bffa8f2292e139fad923dbaf863bc", + "inv": "80682c48982407b489d53d1261b19ec8627d02b8cda5336750b8cee332ae260de57b02d72609c1e0e9f28e2040fc65b6f02d56dbd6aa9af8fde656f70495dfb723ba01173d4707a12fddac628ca29f3e32340bd8f7ddb557cf819f6b01e445ad96f874ba235584ee71f6581f62d4f43bf03f910f6510deb85e8ef06c7f09d9794a008be7ff2529f0ebb69decef646387dc767b74939265fec0223aa6d84d2a8a1cc912d5ca25b4e144ab8f6ba054b54910176d5737a2cff011da431bd5f2a0d2d66b9e70b39f4b050e45c0d9c16f02deda9ddf2d00f3e4b01037d7029cd49c2d46a8e1fc2c0c17520af1f4b5e25ba396afc4cd60c494a4c426448b35b49635b337cfb08e7c22a39b256dd032c00adddafb51a627f99a0e1704170ac1f1912e49d9db10ec04c19c58f420212973e0cb329524223a6aa56c7937c5dffdb5d966b6cd4cbc26f3201dd25c80960a1a111b32947bb78973d269fac7f5186530930ed19f68507540eed9e1bab8b00f00d8ca09b3f099aae46180e04e3584bd7ca054df18a1504b89d1d1675d0966c4ae1407be325cdf623cf13ff13e4a28b594d59e3eadbadf6136eee7a59d6a444c9eb4e2198e8a974f27a39eb63af2c9af3870488b8adaad444674f512133ad80b9220e09158521614f1faadfe8505ef57b7df6813048603f0dd04f4280177a11380fbfc861dbcbd7418d62155248dad5fdec0991f", + "blinded_msg": "4c1b82d9b97b968b2ce0754e326abd49e3d723ed937d84bead34b6a834483b43d510bf62ca47683ed366d94d3d357b270a85cf2cc2ddd171141b45d7549d5373cf67d14f6f462c14ebded906793144faba37f129c0f3172854ec0f854e555552eec5a30c87788f1039814594f04348709e26a883be82affff207b1886b75c037f43f847f45d89bcbf210c22ffcdf8118ce8a526b3723e6209c26319f8f5d2adcf0b637031c9fdf53470a915c587e30287ba88ed4f1cd5e93cf3d4990acf31fffdbfddec80ae0b728d5b4c612a396fd81acaa65566a4dc1c24624f44fd10cdba05f3d0bed2e69bb0d13d41a9f1b4e67aa566520778733ced5e6260f4d1982f63bb835442acffe3cb87f5f8ec6bb84226e0eab787159d08e57604b13557ceea97f2c4ad0631accf898f302df86f0b64354ec0b3bdf1b4e2a4deb4d38f655ea8d80de4cc19aa06ffcd56e348faf894c8774c53235ddcc152d80cf66b417eee4d182781bab8c979937a3c7502d8f39c57c4f09884de5a7247f2539910a96e4b15f9a3df88edc21a13030af357467a99dca50dba4afe4a6185a240ac8f1d8aab2e83443025f94e1af930f56f78661369cc6790701f31b83aec40f96a72c7f7ba13b4ebdd8e24e7351f4ffba0a7c072cb28f13aff06cd02368491044fcc536213b2e3b1cf6ca81cf2097b7b19d2b36bd246f390f53768f1c2e56113ea91b33c7cfa647", + "blind_sig": "4894f64d7214c216282d9842cbf7e7cccd9c0dcb1f4294a6bdeccd4c4c2446160d7cac7892f01b70dfa69f533891d2fbb447f7cf7541d1b504a2d46fc1bb6de26b345972aada8ebce280b906f3a10a13208f77ef896fbe6bc4504327fd4c5c8f03211d45ae9672e9f4be0f4900762ba2a7177a58b90d6dd1263faf2b7a5f15d50a7b00e733742c1b6a1ea4eb5fbfb407abf14496ab26b50cf1a5a56dea616b7a6a5595777400571a751c682b9fdd6badb3f72292f314f4ba2ba0f394f91676a4bb12e60ea08c977f7082be6357c1ca82fe3301fe5fb4128609bee2410db0481aea3a5737fb0bce9381272c2202644f662e99f64bf1190d66e230cc0371ec33fe32fe725dfd872041914d39462a909414a780c9aab394af443199eba56c83986d22d57d4421b41ff8e5bec537d271223adb34d26c64989048a88d8f352a06a7cc153e216a6bed9548bb38d2a1600b2f3403289df6df74aec525ef9e413b7140a7c1a914dedd74a336f1beed39a8e5e2cef76cac094df0dbb3fa55d4b7ee781c74bed3bd8bc7aa6ef3f1dbfa4674945720ec93dafa6d0650229ab75e3fae687327fac081cf4bb376e02a2b73314c54c12f88572c28980f13aba5731bc5a3a60575ea116c8ea2fe5009168deb1255026c9310783ff7f644255d3e1691e194db1babd7780b9a5dc0cb3de2b700d12f49cbe4db51ca2f3c8a58b09e854cc71e8070ab", + "sig": "195363ba25e4bf763f6538c86865785f93f4ea6092da3ad200d41b99eb0eb0869fa792df619fd8fa5923d5d03d5882faae6d25054118deef5e4a6a252dd5afb0dac262b74c391090b1575fbafd959d26bc294f47fb45a2c1c209932c4f94b24394eded91fbdd015e1a85dde63c9e77a0283f812cad1192d86432c51331e46fd4f3771bbafb929f847a19cb05e5f79b6b519d67e8f005951e53656be97cb612d2f506618b366403b34648451d6fbc7318c2f3f583cc6fa17bf2108398f9284e0602187904406a9322f1e7b8016ca9ad11b835756df862c465c420535e25faa48bf341f7ee8192be47fa875791f32f56d5e631d237060688f052426dee5b0b2b74ca5f830e82a453379eedb541fa4fcdaa19dae6509401e3cdd4c40f5c9243db3f6d7115c4e8cd6db8290723ab01d9d0d7e355a97a01547800e43f11736668c3f8908848d759c33a67a2f506abc3f6871cbe625b1bc71eb06d785a59501396712c581a60d6ccc450d2f4eb4cf08ae0dbfa45c2860425be90cc4cd4c989495bbd2963e19c59ae5d90d1ca884e80d654b5f2cd6a80c3588b514ee91c802736f594c340397b316a97e9c70b0609955b6c3ee06f4760d9377f0797a0411a244db395bb8b711ef79fbcb5589226174029be79a72dcd6f4ca566b7b1b9a27e43b5c02a9a579d60bdda183398d66d76e0e8eceb1af2f27633589d043bcdc041683b31f7f1" + }, + { + "name": "RSABSSA-SHA384-PSS-Deterministic", + "p": "e1f4d7a34802e27c7392a3cea32a262a34dc3691bd87f3f310dc75673488930559c120fd0410194fb8a0da55bd0b81227e843fdca6692ae80e5a5d414116d4803fca7d8c30eaaae57e44a1816ebb5c5b0606c536246c7f11985d731684150b63c9a3ad9e41b04c0b5b27cb188a692c84696b742a80d3cd00ab891f2457443dadfeba6d6daf108602be26d7071803c67105a5426838e6889d77e8474b29244cefaf418e381b312048b457d73419213063c60ee7b0d81820165864fef93523c9635c22210956e53a8d96322493ffc58d845368e2416e078e5bcb5d2fd68ae6acfa54f9627c42e84a9d3f2774017e32ebca06308a12ecc290c7cd1156dcccfb2311", + "q": "c601a9caea66dc3835827b539db9df6f6f5ae77244692780cd334a006ab353c806426b60718c05245650821d39445d3ab591ed10a7339f15d83fe13f6a3dfb20b9452c6a9b42eaa62a68c970df3cadb2139f804ad8223d56108dfde30ba7d367e9b0a7a80c4fdba2fd9dde6661fc73fc2947569d2029f2870fc02d8325acf28c9afa19ecf962daa7916e21afad09eb62fe9f1cf91b77dc879b7974b490d3ebd2e95426057f35d0a3c9f45f79ac727ab81a519a8b9285932d9b2e5ccd347e59f3f32ad9ca359115e7da008ab7406707bd0e8e185a5ed8758b5ba266e8828f8d863ae133846304a2936ad7bc7c9803879d2fc4a28e69291d73dbd799f8bc238385", + "n": "aec4d69addc70b990ea66a5e70603b6fee27aafebd08f2d94cbe1250c556e047a928d635c3f45ee9b66d1bc628a03bac9b7c3f416fe20dabea8f3d7b4bbf7f963be335d2328d67e6c13ee4a8f955e05a3283720d3e1f139c38e43e0338ad058a9495c53377fc35be64d208f89b4aa721bf7f7d3fef837be2a80e0f8adf0bcd1eec5bb040443a2b2792fdca522a7472aed74f31a1ebe1eebc1f408660a0543dfe2a850f106a617ec6685573702eaaa21a5640a5dcaf9b74e397fa3af18a2f1b7c03ba91a6336158de420d63188ee143866ee415735d155b7c2d854d795b7bc236cffd71542df34234221a0413e142d8c61355cc44d45bda94204974557ac2704cd8b593f035a5724b1adf442e78c542cd4414fce6f1298182fb6d8e53cef1adfd2e90e1e4deec52999bdc6c29144e8d52a125232c8c6d75c706ea3cc06841c7bda33568c63a6c03817f722b50fcf898237d788a4400869e44d90a3020923dc646388abcc914315215fcd1bae11b1c751fd52443aac8f601087d8d42737c18a3fa11ecd4131ecae017ae0a14acfc4ef85b83c19fed33cfd1cd629da2c4c09e222b398e18d822f77bb378dea3cb360b605e5aa58b20edc29d000a66bd177c682a17e7eb12a63ef7c2e4183e0d898f3d6bf567ba8ae84f84f1d23bf8b8e261c3729e2fa6d07b832e07cddd1d14f55325c6f924267957121902dc19b3b32948bdead5", + "e": "010001", + "d": "0d43242aefe1fb2c13fbc66e20b678c4336d20b1808c558b6e62ad16a287077180b177e1f01b12f9c6cd6c52630257ccef26a45135a990928773f3bd2fc01a313f1dac97a51cec71cb1fd7efc7adffdeb05f1fb04812c924ed7f4a8269925dad88bd7dcfbc4ef01020ebfc60cb3e04c54f981fdbd273e69a8a58b8ceb7c2d83fbcbd6f784d052201b88a9848186f2a45c0d2826870733e6fd9aa46983e0a6e82e35ca20a439c5ee7b502a9062e1066493bdadf8b49eb30d9558ed85abc7afb29b3c9bc644199654a4676681af4babcea4e6f71fe4565c9c1b85d9985b84ec1abf1a820a9bbebee0df1398aae2c85ab580a9f13e7743afd3108eb32100b870648fa6bc17e8abac4d3c99246b1f0ea9f7f93a5dd5458c56d9f3f81ff2216b3c3680a13591673c43194d8e6fc93fc1e37ce2986bd628ac48088bc723d8fbe293861ca7a9f4a73e9fa63b1b6d0074f5dea2a624c5249ff3ad811b6255b299d6bc5451ba7477f19c5a0db690c3e6476398b1483d10314afd38bbaf6e2fbdbcd62c3ca9797a420ca6034ec0a83360a3ee2adf4b9d4ba29731d131b099a38d6a23cc463db754603211260e99d19affc902c915d7854554aabf608e3ac52c19b8aa26ae042249b17b2d29669b5c859103ee53ef9bdc73ba3c6b537d5c34b6d8f034671d7f3a8a6966cc4543df223565343154140fd7391c7e7be03e241f4ecfeb877a051", + "msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "msg_prefix": "", + "prepared_msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "salt": "051722b35f458781397c3a671a7d3bd3096503940e4c4f1aaa269d60300ce449555cd7340100df9d46944c5356825abf", + "encoded_msg": "6e0c464d9c2f9fbc147b43570fc4f238e0d0b38870b3addcf7a4217df912ccef17a7f629aa850f63a063925f312d61d6437be954b45025e8282f9c0b1131bc8ff19a8a928d859b37113db1064f92a27f64761c181c1e1f9b251ae5a2f8a4047573b67a270584e089beadcb13e7c82337797119712e9b849ff56e04385d144d3ca9d8d92bf78adb20b5bbeb3685f17038ec6afade3ef354429c51c687b45a7018ee3a6966b3af15c9ba8f40e6461ba0a17ef5a799672ad882bab02b518f9da7c1a962945c2e9b0f02f29b31b9cdf3e633f9d9d2a22e96e1de28e25241ca7dd04147112f578973403e0f4fd80865965475d22294f065e17a1c4a201de93bd14223e6b1b999fd548f2f759f52db71964528b6f15b9c2d7811f2a0a35d534b8216301c47f4f04f412cae142b48c4cdff78bc54df690fd43142d750c671dd8e2e938e6a440b2f825b6dbb3e19f1d7a3c0150428a47948037c322365b7fe6fe57ac88d8f80889e9ff38177bad8c8d8d98db42908b389cb59692a58ce275aa15acb032ca951b3e0a3404b7f33f655b7c7d83a2f8d1b6bbff49d5fcedf2e030e80881aa436db27a5c0dea13f32e7d460dbf01240c2320c2bb5b3225b17145c72d61d47c8f84d1e19417ebd8ce3638a82d395cc6f7050b6209d9283dc7b93fecc04f3f9e7f566829ac41568ef799480c733c09759aa9734e2013d7640dc6151018ea902bc", + "inv": "80682c48982407b489d53d1261b19ec8627d02b8cda5336750b8cee332ae260de57b02d72609c1e0e9f28e2040fc65b6f02d56dbd6aa9af8fde656f70495dfb723ba01173d4707a12fddac628ca29f3e32340bd8f7ddb557cf819f6b01e445ad96f874ba235584ee71f6581f62d4f43bf03f910f6510deb85e8ef06c7f09d9794a008be7ff2529f0ebb69decef646387dc767b74939265fec0223aa6d84d2a8a1cc912d5ca25b4e144ab8f6ba054b54910176d5737a2cff011da431bd5f2a0d2d66b9e70b39f4b050e45c0d9c16f02deda9ddf2d00f3e4b01037d7029cd49c2d46a8e1fc2c0c17520af1f4b5e25ba396afc4cd60c494a4c426448b35b49635b337cfb08e7c22a39b256dd032c00adddafb51a627f99a0e1704170ac1f1912e49d9db10ec04c19c58f420212973e0cb329524223a6aa56c7937c5dffdb5d966b6cd4cbc26f3201dd25c80960a1a111b32947bb78973d269fac7f5186530930ed19f68507540eed9e1bab8b00f00d8ca09b3f099aae46180e04e3584bd7ca054df18a1504b89d1d1675d0966c4ae1407be325cdf623cf13ff13e4a28b594d59e3eadbadf6136eee7a59d6a444c9eb4e2198e8a974f27a39eb63af2c9af3870488b8adaad444674f512133ad80b9220e09158521614f1faadfe8505ef57b7df6813048603f0dd04f4280177a11380fbfc861dbcbd7418d62155248dad5fdec0991f", + "blinded_msg": "10c166c6a711e81c46f45b18e5873cc4f494f003180dd7f115585d871a28930259654fe28a54dab319cc5011204c8373b50a57b0fdc7a678bd74c523259dfe4fd5ea9f52f170e19dfa332930ad1609fc8a00902d725cfe50685c95e5b2968c9a2828a21207fcf393d15f849769e2af34ac4259d91dfd98c3a707c509e1af55647efaa31290ddf48e0133b798562af5eabd327270ac2fb6c594734ce339a14ea4fe1b9a2f81c0bc230ca523bda17ff42a377266bc2778a274c0ae5ec5a8cbbe364fcf0d2403f7ee178d77ff28b67a20c7ceec009182dbcaa9bc99b51ebbf13b7d542be337172c6474f2cd3561219fe0dfa3fb207cff89632091ab841cf38d8aa88af6891539f263adb8eac6402c41b6ebd72984e43666e537f5f5fe27b2b5aa114957e9a580730308a5f5a9c63a1eb599f093ab401d0c6003a451931b6d124180305705845060ebba6b0036154fcef3e5e9f9e4b87e8f084542fd1dd67e7782a5585150181c01eb6d90cb95883837384a5b91dbb606f266059ecc51b5acbaa280e45cfd2eec8cc1cdb1b7211c8e14805ba683f9b78824b2eb005bc8a7d7179a36c152cb87c8219e5569bba911bb32a1b923ca83de0e03fb10fba75d85c55907dda5a2606bf918b056c3808ba496a4d95532212040a5f44f37e1097f26dc27b98a51837daa78f23e532156296b64352669c94a8a855acf30533d8e0594ace7c442", + "blind_sig": "364f6a40dbfbc3bbb257943337eeff791a0f290898a6791283bba581d9eac90a6376a837241f5f73a78a5c6746e1306ba3adab6067c32ff69115734ce014d354e2f259d4cbfb890244fd451a497fe6ecf9aa90d19a2d441162f7eaa7ce3fc4e89fd4e76b7ae585be2a2c0fd6fb246b8ac8d58bcb585634e30c9168a434786fe5e0b74bfe8187b47ac091aa571ffea0a864cb906d0e28c77a00e8cd8f6aba4317a8cc7bf32ce566bd1ef80c64de041728abe087bee6cadd0b7062bde5ceef308a23bd1ccc154fd0c3a26110df6193464fc0d24ee189aea8979d722170ba945fdcce9b1b4b63349980f3a92dc2e5418c54d38a862916926b3f9ca270a8cf40dfb9772bfbdd9a3e0e0892369c18249211ba857f35963d0e05d8da98f1aa0c6bba58f47487b8f663e395091275f82941830b050b260e4767ce2fa903e75ff8970c98bfb3a08d6db91ab1746c86420ee2e909bf681cac173697135983c3594b2def673736220452fde4ddec867d40ff42dd3da36c84e3e52508b891a00f50b4f62d112edb3b6b6cc3dbd546ba10f36b03f06c0d82aeec3b25e127af545fac28e1613a0517a6095ad18a98ab79f68801e05c175e15bae21f821e80c80ab4fdec6fb34ca315e194502b8f3dcf7892b511aee45060e3994cd15e003861bc7220a2babd7b40eda03382548a34a7110f9b1779bf3ef6011361611e6bc5c0dc851e1509de1a", + "sig": "6fef8bf9bc182cd8cf7ce45c7dcf0e6f3e518ae48f06f3c670c649ac737a8b8119a34d51641785be151a697ed7825fdfece82865123445eab03eb4bb91cecf4d6951738495f8481151b62de869658573df4e50a95c17c31b52e154ae26a04067d5ecdc1592c287550bb982a5bb9c30fd53a768cee6baabb3d483e9f1e2da954c7f4cf492fe3944d2fe456c1ecaf0840369e33fb4010e6b44bb1d721840513524d8e9a3519f40d1b81ae34fb7a31ee6b7ed641cb16c2ac999004c2191de0201457523f5a4700dd649267d9286f5c1d193f1454c9f868a57816bf5ff76c838a2eeb616a3fc9976f65d4371deecfbab29362caebdff69c635fe5a2113da4d4d8c24f0b16a0584fa05e80e607c5d9a2f765f1f069f8d4da21f27c2a3b5c984b4ab24899bef46c6d9323df4862fe51ce300fca40fb539c3bb7fe2dcc9409e425f2d3b95e70e9c49c5feb6ecc9d43442c33d50003ee936845892fb8be475647da9a080f5bc7f8a716590b3745c2209fe05b17992830ce15f32c7b22cde755c8a2fe50bd814a0434130b807dc1b7218d4e85342d70695a5d7f29306f25623ad1e8aa08ef71b54b8ee447b5f64e73d09bdd6c3b7ca224058d7c67cc7551e9241688ada12d859cb7646fbd3ed8b34312f3b49d69802f0eaa11bc4211c2f7a29cd5c01ed01a39001c5856fab36228f5ee2f2e1110811872fe7c865c42ed59029c706195d52" + }, + { + "name": "RSABSSA-SHA384-PSSZERO-Deterministic", + "p": "e1f4d7a34802e27c7392a3cea32a262a34dc3691bd87f3f310dc75673488930559c120fd0410194fb8a0da55bd0b81227e843fdca6692ae80e5a5d414116d4803fca7d8c30eaaae57e44a1816ebb5c5b0606c536246c7f11985d731684150b63c9a3ad9e41b04c0b5b27cb188a692c84696b742a80d3cd00ab891f2457443dadfeba6d6daf108602be26d7071803c67105a5426838e6889d77e8474b29244cefaf418e381b312048b457d73419213063c60ee7b0d81820165864fef93523c9635c22210956e53a8d96322493ffc58d845368e2416e078e5bcb5d2fd68ae6acfa54f9627c42e84a9d3f2774017e32ebca06308a12ecc290c7cd1156dcccfb2311", + "q": "c601a9caea66dc3835827b539db9df6f6f5ae77244692780cd334a006ab353c806426b60718c05245650821d39445d3ab591ed10a7339f15d83fe13f6a3dfb20b9452c6a9b42eaa62a68c970df3cadb2139f804ad8223d56108dfde30ba7d367e9b0a7a80c4fdba2fd9dde6661fc73fc2947569d2029f2870fc02d8325acf28c9afa19ecf962daa7916e21afad09eb62fe9f1cf91b77dc879b7974b490d3ebd2e95426057f35d0a3c9f45f79ac727ab81a519a8b9285932d9b2e5ccd347e59f3f32ad9ca359115e7da008ab7406707bd0e8e185a5ed8758b5ba266e8828f8d863ae133846304a2936ad7bc7c9803879d2fc4a28e69291d73dbd799f8bc238385", + "n": "aec4d69addc70b990ea66a5e70603b6fee27aafebd08f2d94cbe1250c556e047a928d635c3f45ee9b66d1bc628a03bac9b7c3f416fe20dabea8f3d7b4bbf7f963be335d2328d67e6c13ee4a8f955e05a3283720d3e1f139c38e43e0338ad058a9495c53377fc35be64d208f89b4aa721bf7f7d3fef837be2a80e0f8adf0bcd1eec5bb040443a2b2792fdca522a7472aed74f31a1ebe1eebc1f408660a0543dfe2a850f106a617ec6685573702eaaa21a5640a5dcaf9b74e397fa3af18a2f1b7c03ba91a6336158de420d63188ee143866ee415735d155b7c2d854d795b7bc236cffd71542df34234221a0413e142d8c61355cc44d45bda94204974557ac2704cd8b593f035a5724b1adf442e78c542cd4414fce6f1298182fb6d8e53cef1adfd2e90e1e4deec52999bdc6c29144e8d52a125232c8c6d75c706ea3cc06841c7bda33568c63a6c03817f722b50fcf898237d788a4400869e44d90a3020923dc646388abcc914315215fcd1bae11b1c751fd52443aac8f601087d8d42737c18a3fa11ecd4131ecae017ae0a14acfc4ef85b83c19fed33cfd1cd629da2c4c09e222b398e18d822f77bb378dea3cb360b605e5aa58b20edc29d000a66bd177c682a17e7eb12a63ef7c2e4183e0d898f3d6bf567ba8ae84f84f1d23bf8b8e261c3729e2fa6d07b832e07cddd1d14f55325c6f924267957121902dc19b3b32948bdead5", + "e": "010001", + "d": "0d43242aefe1fb2c13fbc66e20b678c4336d20b1808c558b6e62ad16a287077180b177e1f01b12f9c6cd6c52630257ccef26a45135a990928773f3bd2fc01a313f1dac97a51cec71cb1fd7efc7adffdeb05f1fb04812c924ed7f4a8269925dad88bd7dcfbc4ef01020ebfc60cb3e04c54f981fdbd273e69a8a58b8ceb7c2d83fbcbd6f784d052201b88a9848186f2a45c0d2826870733e6fd9aa46983e0a6e82e35ca20a439c5ee7b502a9062e1066493bdadf8b49eb30d9558ed85abc7afb29b3c9bc644199654a4676681af4babcea4e6f71fe4565c9c1b85d9985b84ec1abf1a820a9bbebee0df1398aae2c85ab580a9f13e7743afd3108eb32100b870648fa6bc17e8abac4d3c99246b1f0ea9f7f93a5dd5458c56d9f3f81ff2216b3c3680a13591673c43194d8e6fc93fc1e37ce2986bd628ac48088bc723d8fbe293861ca7a9f4a73e9fa63b1b6d0074f5dea2a624c5249ff3ad811b6255b299d6bc5451ba7477f19c5a0db690c3e6476398b1483d10314afd38bbaf6e2fbdbcd62c3ca9797a420ca6034ec0a83360a3ee2adf4b9d4ba29731d131b099a38d6a23cc463db754603211260e99d19affc902c915d7854554aabf608e3ac52c19b8aa26ae042249b17b2d29669b5c859103ee53ef9bdc73ba3c6b537d5c34b6d8f034671d7f3a8a6966cc4543df223565343154140fd7391c7e7be03e241f4ecfeb877a051", + "msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "msg_prefix": "", + "prepared_msg": "8f3dc6fb8c4a02f4d6352edf0907822c1210a9b32f9bdda4c45a698c80023aa6b59f8cfec5fdbb36331372ebefedae7d", + "salt": "", + "encoded_msg": "159499b90471b496c2639ec482e99feaba525c0420c565d17dc60c1bb1f47703f04436cceaa8f69811e1bf8546fa971226c9e71421b32b571ed5ea0e032269d4219b4404316eb17a58f277634aeed394b7f3888153b5bb163e40807e605dafdd1789dd473b0846bdcb6524417bc3a35366fab4261708c0e4b4beba07a1a64bbccb4b1ac215d1350a50a501e8e96612028b535ad731abf1f117ee07d07a4de9cef3d70f5845ba84c29d5d92c6e66a1f9489a5f527b846825360fd6e90f40ed041c682e489f3acde984a3ea580181418c1d15017af2657bc4b70485cdc0f1ebc3693e0d70a5d01f37ff640993fa071274fb9ee44e0c24dcb58ffa21a9a6540d87f24379beaafcc3b4bd42c45ec6820e03738ce98bea11c71685f31db63429fab8658bdb816f1ecccb1888f2402de0bd2f0f9646decdcad4c11b41428eec1ed25f2a86d43bb04f95726bfbd98ea34ca091b7adbabd0e28f17fa0345b89542d23c3530554987508a23641bd4f9e52962b0bee3ac9ffe005322d26a39941c5847774300411c69635f96903e8d593530908bd92a4fa6a2d52f88073a647a4b3894b7e4ebb80699e60227397bfa93f41b1c97e107b632f68e70409372ead2f072c11cf99be4486fcbf763dde28ee156db26cd358a69fcb79644f1f2fcc166f41a4c80f5851ee08be051f14b601418d6e56e61733b9b210c6bef17edac121a754d19b9bc", + "inv": "55f2053e9a4309ac61ac4da7f3a314e626f362e95f30337962d12f08b343165c8dea34d7812dc2dcb227cfa8de49bca57880ac55f6d77b37ed83a32eb33656ddf0cde29761aef9f86bd758280b3403a63b466831cba4c97e17e9a11e4139f9d84e5912b017eafbafdbb3ae59a1424feae6914eb1bf20922c6db5da8a538752b3b662ae15cae7beac9a0362b8836001c57b0c5167dceb9a66e6ab6a90e9898646b4274c3662e4316926c4da7caf5aeff611934b70581280ec68fb2ce04c5681ef95b086b7289afae8ecd669325659791853a9f4c0b784f6f60b212c3b39754d5539e3671d7930d1272e82b3853b6583a83d9ff70c00ce1938c05eccee531cb075564059b2749e84b45dff7d179c69c86c5d1870aeffd6281d099838a3a988ff9e2684f6cc896b5326275309187d9e3558163131e4d247c2ec8317a2c09f8079d32db8241c869bc5f773722ed8e68bfa5c518d20b955abf02103fce1a025149b14670fdfc8a3f0089516db047f86b9be626ff44989d6fcc162c9570da5b862b47304eca2aceba4dedd6a672458aae779004fe116009600a6a52eb6161a3d09fda09963b56f2870a150df7183bfa03ce735513e637631fb4f980657a8cdb953b2156594607f8ebf7de6999626197072afd7ff60a5d2f782dabe026e0f298df141b8a276aaf7202d959088d7721786b04c79e45c807eb46fcf3a94031ef351aff644", + "blinded_msg": "0c86f078fe8fd2ea6b4e120d3fef7555701a7c6b7bd5606a7fb2ef2769d119f2639477a7904984d67f0ecf419059aac58041977871d8da253a1aee14cde49cfb919f502f4d79d56d473a95f450982ad83398c1f3dd3a3342a18df9e81447998eae6c7f9de94148a30de0846fc2402b17b2dfe233c450ba41f141ec14b27bf4e7d79a5c0fa23ad64c2d2fa33691a3048d835f7e477ecba458e4d58f8dbbcfec2a484e1442ab4b266cfc610fec95f6258ef137590254931dea30f58e96a64cef7aca013cb037259d4dec8a2298d3e2ce96c75a10f39dcdfe7e90eba200c73fc3f5fbbdc4d50d33990559504d0ddb4fe50407fc21321128f72866c780d1412f20d4788ad0ebc2077dca4ae87108e416c3510609867196f4fbb69ff6c3a4c0249e3d6bcf157636666a0e17d8dba9034d9875e40bbff075b0a936acd75baf15179042959d6b27f8e233b60db93a2abce81f47e259f76b5a68d58c21fd8ccd7e102fc9292ec5a1bad8618a94f09ca6a58b1c5c7062fb17bd62035d898b76ead5f52a9869d5b6fbbbf5cd07bc3c35adbff4f03949fe32b455cd5b3de07859d65045b72fb1f4a0ab5c80a27a60b57ebd9e0b173778d3be592e74cdc6a9ffa147cbb021a87b9a525bc9135114d4daacf0b111773551474ea98493ed8562dac1c9e6398ada60573ff550a01aa4468fd493fb69b3a98ab3790fc7f71ef5dfa3f1979ebe35af", + "blind_sig": "5ca77254ce107e6e6eedcf8ca03e08d4e92eeb0f4f08b2a2e7fb69da2f5db95f2167ce58a861e45a5cac1bf7d3df3edd64a2802bb5c16ceb62b2f5a0355c0d0f6d8270b658fa26e86afc18a88e91b0ec07e813d50ed4fb20376bf8470179a3a97d5a29f9f9fe931d6bff233c45d62cd91cdb9a692cda309fad962fd9f7f19f89cc48bc75f9b521aeca21921330c7e91ff7ff2af6e62fe3112f7ec675e866c5961556a1796f2fd4707dd9fcde702caf003b5acfde1cd97bc5d2a63d126ac0587bf8ed6a3064d20dbdef9e207423e678f36e516e4c2696cc74f0a74be4c3ddaaf6cdbc95c9d58d930f0f4e00dfa2bf5d0a333964ec03226073030b9b78210d3160ec2722abf3c01efa1636a28c6c5ac9d14913537322ee42d26ab26518ec2af03202ea0e190a4790b7a8951be98313000c62d1fe0ea05647c451348f97ef5ced6c6e83303aececcc508fcc8f18f7751e050f9f7a562f45b0d03159486d067ab4b3df1b0f270d009436f0305640929a2b61cfeef24a2e39a9a622c9d9d9e2c99245ea415243f472b226e068ebba7624ccf012b86b21d80cb2e3b718224b2f7b638a16b7665a1a493b014dd3d0f7b97ca290665b1f0972bc4a7d4051e843182771b6258d9d63f919fde109f8487f443ea54518c053acfbf7c0cfe60435b6966d42c034cf6ad3be2281fa2bf1a90f1d2cba55643e9ae37065a7534f53402e6f4c2a3a", + "sig": "4454b6983ff01cb28545329f394936efa42ed231e15efbc025fdaca00277acf0c8e00e3d8b0ecebd35b057b8ebfc14e1a7097368a4abd20b555894ccef3d1b9528c6bcbda6b95376bef230d0f1feff0c1064c62c60a7ae7431d1fdfa43a81eed9235e363e1ffa0b2797aba6aad6082fcd285e14fc8b71de6b9c87cb4059c7dc1e96ae1e63795a1e9af86b9073d1d848aef3eca8a03421bcd116572456b53bcfd4dabb0a9691f1fabda3ed0ce357aee2cfee5b1a0eb226f69716d4e011d96eede5e38a9acb531a64336a0d5b0bae3ab085b658692579a376740ff6ce69e89b06f360520b864e33d82d029c808248a19e18e31f0ecd16fac5cd4870f8d3ebc1c32c718124152dc905672ab0b7af48bf7d1ac1ff7b9c742549c91275ab105458ae37621757add83482bbcf779e777bbd61126e93686635d4766aedf5103cf7978f3856ccac9e28d21a850dbb03c811128616d315d717be1c2b6254f8509acae862042c034530329ce15ca2e2f6b1f5fd59272746e3918c748c0eb810bf76884fa10fcf749326bbfaa5ba285a0186a22e4f628dbf178d3bb5dc7e165ca73f6a55ecc14c4f5a26c4693ce5da032264cbec319b12ddb9787d0efa4fcf1e5ccee35ad85ecd453182df9ed735893f830b570faae8be0f6fe2e571a4e0d927cba4debd368d3b4fca33ec6251897a137cf75474a32ac8256df5e5ffa518b88b43fb6f63a24" + } +]