Skip to content

Commit a8a02ac

Browse files
Use more efficient serialization and deserialization for ARC types (#336)
### Motivation The use of `Data.subdata(in:)` results in needless copies when decoding fields from bytes. Additionally, the use of a long chain of `Data.append(_:)` without a call to `Data.init(capacity:)` or `Data.reserveCapacity(_:)` can result in needless reallocations. ### Modifications - Remove unused `EncodeInt` and `DecodeInt` functions. They were unused before this PR but are there from when ARC credential serialization included the presentation limit. `DecodeInt` also happened to be incorrect and would crash when called with a slice. - Add some useful utility methods for popping bytes from a view without copying. - Add some generic functions for encoding and decoding `FixedWidthInteger` types along with tests for concrete types over `Data` and slices. - In ARC decoding, replace the use of `Data.subdata(in:)` with code that uses the `Data.Subsequence` slice (which happens to be `Data` since the type is self-slicing). - In ARC encoding, reserve enough capacity in the resulting `Data` when the sizes are known to reduce allocations. ## Results More performant serialization and deserialization for ARC types.
1 parent fca0473 commit a8a02ac

File tree

5 files changed

+343
-132
lines changed

5 files changed

+343
-132
lines changed

.swiftformatignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Sources/_CryptoExtras/Util/BoringSSLHelpers.swift
9999
Sources/_CryptoExtras/Util/DigestType.swift
100100
Sources/_CryptoExtras/Util/Error.swift
101101
Sources/_CryptoExtras/Util/I2OSP.swift
102+
Sources/_CryptoExtras/Util/IntegerEncoding.swift
102103
Sources/_CryptoExtras/Util/PEMDocument.swift
103104
Sources/_CryptoExtras/Util/PrettyBytes.swift
104105
Sources/_CryptoExtras/Util/SubjectPublicKeyInfo.swift
@@ -151,6 +152,7 @@ Tests/_CryptoExtrasTests/TestRSABlindSigning.swift
151152
Tests/_CryptoExtrasTests/TestRSABlindSigningAPI.swift
152153
Tests/_CryptoExtrasTests/TestRSAEncryption.swift
153154
Tests/_CryptoExtrasTests/TestRSASigning.swift
155+
Tests/_CryptoExtrasTests/Util/IntegerEncodingTests.swift
154156
Tests/_CryptoExtrasTests/Utils/BytesUtil.swift
155157
Tests/_CryptoExtrasTests/Utils/RFCVector.swift
156158
Tests/_CryptoExtrasTests/Utils/SplitData.swift

Sources/_CryptoExtras/ARC/ARCEncoding.swift

Lines changed: 99 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -19,94 +19,69 @@ typealias ARCCurve = P384
1919
@available(macOS 10.15, iOS 13.2, tvOS 13.2, watchOS 6.1, *)
2020
typealias ARCH2G = HashToCurveImpl<ARCCurve>
2121

22-
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
23-
internal func DecodeInt(value: Data) -> Int {
24-
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
25-
var result = Int(0)
26-
27-
for i in 0..<value.count {
28-
result = result << 8
29-
result += Int(value[i])
30-
}
31-
32-
return result
33-
}
34-
35-
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
36-
internal func EncodeInt(value: Int) -> Data {
37-
return I2OSP(value: value, outputByteCount: 4)
38-
}
39-
4022
@available(macOS 10.15, iOS 13.2, tvOS 13.2, watchOS 6.1, *)
4123
extension ARC.CredentialRequest where H2G == ARCH2G {
4224
static let scalarCount = 5
43-
static let pointCount = 2
44-
static let serializedByteCount = pointCount * ARCCurve.compressedx962PointByteCount + scalarCount * ARCCurve.orderByteCount
25+
static let serializedByteCount = 2 * ARCCurve.compressedx962PointByteCount + Self.scalarCount * ARCCurve.orderByteCount
4526

4627
func serialize() -> Data {
47-
let m1Enc = self.m1Enc.compressedRepresentation
48-
let m2Enc = self.m2Enc.compressedRepresentation
49-
let proofData = self.proof.serialize()
50-
return m1Enc + m2Enc + proofData
28+
var result = Data(capacity: Self.serializedByteCount)
29+
result.append(self.m1Enc.compressedRepresentation)
30+
result.append(self.m2Enc.compressedRepresentation)
31+
result.append(self.proof.serialize())
32+
return result
5133
}
5234

5335
static func deserialize<D: DataProtocol>(requestData: D) throws -> ARC.CredentialRequest<ARCH2G> {
54-
guard requestData.count == self.serializedByteCount else {
36+
guard requestData.count == Self.serializedByteCount else {
5537
throw ARC.Errors.incorrectRequestDataSize
5638
}
57-
let requestData = Data(requestData)
5839

59-
var startPointer = 0
60-
let m1Enc = try ARCH2G.G.Element(oprfRepresentation: requestData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
61-
startPointer += ARCCurve.compressedx962PointByteCount
62-
let m2Enc = try ARCH2G.G.Element(oprfRepresentation: requestData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
63-
startPointer += ARCCurve.compressedx962PointByteCount
40+
var bytes = Data(requestData)
41+
42+
let m1Enc = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
43+
let m2Enc = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
44+
let proof = try Proof.deserialize(proofData: bytes, scalarCount: Self.scalarCount)
6445

65-
let proof = try Proof.deserialize(proofData: requestData.subdata(in: startPointer..<startPointer + self.scalarCount * ARCCurve.orderByteCount), scalarCount: self.scalarCount)
6646
return ARC.CredentialRequest(m1Enc: m1Enc, m2Enc: m2Enc, proof: proof)
6747
}
6848
}
6949

7050
@available(macOS 10.15, iOS 13.2, tvOS 13.2, watchOS 6.1, *)
7151
extension ARC.CredentialResponse where H2G == ARCH2G {
7252
static let scalarCount = 8
73-
static let pointCount = 6
74-
static let serializedByteCount = pointCount * ARCCurve.compressedx962PointByteCount + scalarCount * ARCCurve.orderByteCount
53+
static let serializedByteCount = 6 * ARCCurve.compressedx962PointByteCount + Self.scalarCount * ARCCurve.orderByteCount
7554

7655
func serialize() -> Data {
77-
let U = self.U.compressedRepresentation
78-
let encUPrime = self.encUPrime.compressedRepresentation
79-
let X0Aux = self.X0Aux.compressedRepresentation
80-
let X1Aux = self.X1Aux.compressedRepresentation
81-
let X2Aux = self.X2Aux.compressedRepresentation
82-
let HAux = self.HAux.compressedRepresentation
83-
let responsePoints = U + encUPrime + X0Aux + X1Aux + X2Aux + HAux
84-
85-
let proofData = self.proof.serialize()
86-
return responsePoints + proofData
56+
var result = Data(capacity: Self.serializedByteCount)
57+
58+
result.append(self.U.compressedRepresentation)
59+
result.append(self.encUPrime.compressedRepresentation)
60+
result.append(self.X0Aux.compressedRepresentation)
61+
result.append(self.X1Aux.compressedRepresentation)
62+
result.append(self.X2Aux.compressedRepresentation)
63+
result.append(self.HAux.compressedRepresentation)
64+
result.append(self.proof.serialize())
65+
66+
return result
8767
}
8868

8969
static func deserialize<D: DataProtocol>(responseData: D) throws -> ARC.CredentialResponse<ARCH2G> {
9070
guard responseData.count == self.serializedByteCount else {
9171
throw ARC.Errors.incorrectResponseDataSize
9272
}
93-
let responseData = Data(responseData)
94-
95-
var startPointer = 0
96-
let U = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
97-
startPointer += ARCCurve.compressedx962PointByteCount
98-
let encUPrime = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
99-
startPointer += ARCCurve.compressedx962PointByteCount
100-
let X0Aux = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
101-
startPointer += ARCCurve.compressedx962PointByteCount
102-
let X1Aux = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
103-
startPointer += ARCCurve.compressedx962PointByteCount
104-
let X2Aux = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
105-
startPointer += ARCCurve.compressedx962PointByteCount
106-
let HAux = try ARCH2G.G.Element(oprfRepresentation: responseData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
107-
startPointer += ARCCurve.compressedx962PointByteCount
108-
109-
let proof = try Proof.deserialize(proofData: responseData.subdata(in: startPointer..<startPointer + self.scalarCount * ARCCurve.orderByteCount), scalarCount: self.scalarCount)
73+
74+
var bytes = Data(responseData)
75+
76+
let U = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
77+
let encUPrime = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
78+
let X0Aux = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
79+
let X1Aux = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
80+
let X2Aux = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
81+
let HAux = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
82+
83+
let proof = try Proof.deserialize(proofData: bytes, scalarCount: self.scalarCount)
84+
11085
return ARC.CredentialResponse(U: U, encUPrime: encUPrime, X0Aux: X0Aux, X1Aux: X1Aux, X2Aux: X2Aux, HAux: HAux, proof: proof)
11186
}
11287
}
@@ -118,65 +93,59 @@ extension ARC.Presentation where H2G == ARCH2G {
11893
static let serializedByteCount = pointCount * ARCCurve.compressedx962PointByteCount + scalarCount * ARCCurve.orderByteCount
11994

12095
func serialize() -> Data {
121-
// Serialize presentation elements
122-
let U = self.U.compressedRepresentation
123-
let UPrimeCommit = self.UPrimeCommit.compressedRepresentation
124-
let m1Commit = self.m1Commit.compressedRepresentation
125-
let tag = self.tag.compressedRepresentation
126-
127-
let presentationProofData = self.proof.serialize()
128-
return U + UPrimeCommit + m1Commit + tag + presentationProofData
96+
var result = Data(capacity: Self.serializedByteCount)
97+
98+
result.append(self.U.compressedRepresentation)
99+
result.append(self.UPrimeCommit.compressedRepresentation)
100+
result.append(self.m1Commit.compressedRepresentation)
101+
result.append(self.tag.compressedRepresentation)
102+
result.append(self.proof.serialize())
103+
104+
return result
129105
}
130106

131107
static func deserialize<D: DataProtocol>(presentationData: D) throws -> ARC.Presentation<ARCH2G> {
132108
guard presentationData.count == self.serializedByteCount else {
133109
throw ARC.Errors.incorrectPresentationDataSize
134110
}
135-
let presentationData = Data(presentationData)
136-
137-
var startPointer = 0
138-
// Deserialize presentation elements
139-
let U = try ARCH2G.G.Element(oprfRepresentation: presentationData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
140-
startPointer += ARCCurve.compressedx962PointByteCount
141-
let UPrimeCommit = try ARCH2G.G.Element(oprfRepresentation: presentationData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
142-
startPointer += ARCCurve.compressedx962PointByteCount
143-
let m1Commit = try ARCH2G.G.Element(oprfRepresentation: presentationData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
144-
startPointer += ARCCurve.compressedx962PointByteCount
145-
let tag = try ARCH2G.G.Element(oprfRepresentation: presentationData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
146-
startPointer += ARCCurve.compressedx962PointByteCount
147-
148-
let presentationProof = try Proof.deserialize(proofData: presentationData.subdata(in: startPointer..<startPointer + self.scalarCount * ARCCurve.orderByteCount), scalarCount: self.scalarCount)
111+
112+
var bytes = Data(presentationData)
113+
114+
let U = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
115+
let UPrimeCommit = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
116+
let m1Commit = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
117+
let tag = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
118+
let presentationProof = try Proof.deserialize(proofData: bytes, scalarCount: Self.scalarCount)
119+
149120
return ARC.Presentation(U: U, UPrimeCommit: UPrimeCommit, m1Commit: m1Commit, tag: tag, proof: presentationProof)
150121
}
151122
}
152123

153124
@available(macOS 10.15, iOS 13.2, tvOS 13.2, watchOS 6.1, *)
154125
extension ARC.ServerPublicKey where H2G == ARCH2G {
155-
static let pointCount = 3
126+
static let serializedByteCount = 3 * ARCCurve.compressedx962PointByteCount
127+
static let pointCount = 3 // TODO: delete
156128

157129
func serialize() -> Data {
158-
// Serialize server commitment elements
159-
let X0 = self.X0.compressedRepresentation
160-
let X1 = self.X1.compressedRepresentation
161-
let X2 = self.X2.compressedRepresentation
130+
var result = Data(capacity: Self.serializedByteCount)
162131

163-
return X0 + X1 + X2
132+
result.append(self.X0.compressedRepresentation)
133+
result.append(self.X1.compressedRepresentation)
134+
result.append(self.X2.compressedRepresentation)
135+
136+
return result
164137
}
165138

166139
static func deserialize<D: DataProtocol>(serverPublicKeyData: D) throws -> ARC.ServerPublicKey<ARCH2G> {
167140
guard serverPublicKeyData.count == self.pointCount * ARCCurve.compressedx962PointByteCount else {
168141
throw ARC.Errors.incorrectServerCommitmentsSize
169142
}
170-
let serverPublicKeyData = Data(serverPublicKeyData)
171-
var startPointer = 0
172143

173-
// Deserialize server commitment elements
174-
let X0 = try ARCH2G.G.Element(oprfRepresentation: serverPublicKeyData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
175-
startPointer += ARCCurve.compressedx962PointByteCount
176-
let X1 = try ARCH2G.G.Element(oprfRepresentation: serverPublicKeyData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
177-
startPointer += ARCCurve.compressedx962PointByteCount
178-
let X2 = try ARCH2G.G.Element(oprfRepresentation: serverPublicKeyData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
179-
startPointer += ARCCurve.compressedx962PointByteCount
144+
var bytes = Data(serverPublicKeyData)
145+
146+
let X0 = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
147+
let X1 = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
148+
let X2 = try H2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
180149

181150
return ARC.ServerPublicKey(X0: X0, X1: X1, X2: X2)
182151
}
@@ -201,19 +170,18 @@ extension Proof where H2G == ARCH2G {
201170
guard proofData.count == scalarCount * ARCCurve.orderByteCount else {
202171
throw ARC.Errors.incorrectProofDataSize
203172
}
204-
let proofData = Data(proofData)
205-
var startPointer = 0
173+
174+
var bytes = Data(proofData)
206175

207176
// Deserialize challenge
208-
let challenge = try GroupImpl<ARCCurve>.Scalar(bytes: proofData.subdata(in: startPointer..<startPointer+ARCCurve.orderByteCount))
209-
startPointer += ARCCurve.orderByteCount
177+
let challenge = try ARCH2G.G.Scalar(bytes: bytes.popFirst(ARCCurve.orderByteCount))
210178

211179
// Deserialize responses
212180
var responses: [GroupImpl<ARCCurve>.Scalar] = []
181+
responses.reserveCapacity(scalarCount - 1)
213182
for _ in (0..<scalarCount-1) {
214-
let response = try GroupImpl<ARCCurve>.Scalar(bytes: proofData.subdata(in: startPointer..<startPointer+ARCCurve.orderByteCount))
183+
let response = try ARCH2G.G.Scalar(bytes: bytes.popFirst(ARCCurve.orderByteCount))
215184
responses.append(response)
216-
startPointer += ARCCurve.orderByteCount
217185
}
218186

219187
return Proof(challenge: challenge, responses: responses)
@@ -227,42 +195,41 @@ extension ARC.Credential where H2G == ARCH2G {
227195
static let scalarCount = 1
228196
static let pointCount = 5
229197

198+
static let serializedByteCountExcludingPresentationState = 1 * ARCCurve.orderByteCount + 5 * ARCCurve.compressedx962PointByteCount
199+
230200
func serialize() throws -> Data {
231-
let m1 = self.m1.rawRepresentation
232-
let U = self.U.compressedRepresentation
233-
let UPrime = self.UPrime.compressedRepresentation
234-
let X1 = self.X1.compressedRepresentation
235-
let genG = self.generatorG.compressedRepresentation
236-
let genH = self.generatorH.compressedRepresentation
237-
let presentationState = try self.presentationState.serialize()
238-
239-
return m1 + U + UPrime + X1 + genG + genH + presentationState
201+
let presentationStateBytes = try self.presentationState.serialize()
202+
203+
var result = Data(capacity: Self.serializedByteCountExcludingPresentationState + presentationStateBytes.count)
204+
205+
result.append(self.m1.rawRepresentation)
206+
result.append(self.U.compressedRepresentation)
207+
result.append(self.UPrime.compressedRepresentation)
208+
result.append(self.X1.compressedRepresentation)
209+
result.append(self.generatorG.compressedRepresentation)
210+
result.append(self.generatorH.compressedRepresentation)
211+
result.append(presentationStateBytes)
212+
213+
return result
240214
}
241215

242216
static func deserialize<D: DataProtocol>(credentialData: D) throws -> ARC.Credential<ARCH2G> {
243-
let stateByteCount = credentialData.count - self.scalarCount * ARCCurve.orderByteCount - self.pointCount * ARCCurve.compressedx962PointByteCount
244-
guard stateByteCount >= 0 else {
217+
guard credentialData.count - Self.serializedByteCountExcludingPresentationState >= 0 else {
245218
throw ARC.Errors.incorrectCredentialDataSize
246219
}
247220
let credentialData = Data(credentialData)
248221

249-
var startPointer = 0
250-
let m1 = try GroupImpl<ARCCurve>.Scalar(bytes: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.orderByteCount))
251-
startPointer += ARCCurve.orderByteCount
252-
let U = try ARCH2G.G.Element(oprfRepresentation: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
253-
startPointer += ARCCurve.compressedx962PointByteCount
254-
let UPrime = try ARCH2G.G.Element(oprfRepresentation: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
255-
startPointer += ARCCurve.compressedx962PointByteCount
256-
let X1 = try ARCH2G.G.Element(oprfRepresentation: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
257-
startPointer += ARCCurve.compressedx962PointByteCount
258-
let genG = try ARCH2G.G.Element(oprfRepresentation: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
259-
startPointer += ARCCurve.compressedx962PointByteCount
260-
let genH = try ARCH2G.G.Element(oprfRepresentation: credentialData.subdata(in: startPointer..<startPointer+ARCCurve.compressedx962PointByteCount))
261-
startPointer += ARCCurve.compressedx962PointByteCount
262-
263-
// Deserialize presentationState
264-
let presentationStateData = credentialData.subdata(in: startPointer..<startPointer+stateByteCount)
265-
let presentationState = try ARC.PresentationState.deserialize(presentationStateData: presentationStateData)
222+
var bytes = Data(credentialData)
223+
224+
let m1 = try ARCH2G.G.Scalar(bytes: bytes.popFirst(ARCCurve.orderByteCount))
225+
let U = try ARCH2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
226+
let UPrime = try ARCH2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
227+
let X1 = try ARCH2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
228+
let genG = try ARCH2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
229+
let genH = try ARCH2G.G.Element(oprfRepresentation: bytes.popFirst(ARCCurve.compressedx962PointByteCount))
230+
231+
// Deserialize presentationState from remaining bytes.
232+
let presentationState = try ARC.PresentationState.deserialize(presentationStateData: bytes)
266233

267234
let ciphersuite = ARC.Ciphersuite(ARCH2G.self)
268235
return ARC.Credential(m1: m1, U: U, UPrime: UPrime, X1: X1, ciphersuite: ciphersuite, generatorG: genG, generatorH: genH, presentationState: presentationState)

Sources/_CryptoExtras/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ add_library(_CryptoExtras
5656
"Util/DigestType.swift"
5757
"Util/Error.swift"
5858
"Util/I2OSP.swift"
59+
"Util/IntegerEncoding.swift"
5960
"Util/PEMDocument.swift"
6061
"Util/PrettyBytes.swift"
6162
"Util/SubjectPublicKeyInfo.swift"

0 commit comments

Comments
 (0)