Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Test vectors refactor (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
amika-sq authored Jan 5, 2024
1 parent 75ddbac commit 78d57fa
Show file tree
Hide file tree
Showing 25 changed files with 441 additions and 278 deletions.
23 changes: 20 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,40 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-custom-dump.git", from: "1.1.2"),
],
targets: [
// Main tbDEX library target
.target(
name: "tbDEX",
dependencies: [
.product(name: "secp256k1", package: "secp256k1.swift"),
.product(name: "ExtrasBase64", package: "swift-extras-base64"),
]
),
// Shared test utilities target
.target(
name: "TestUtilities",
path: "TestUtilities/"
),
// Main tbDEX test target
.testTarget(
name: "tbDEXTests",
dependencies: [
"tbDEX",
"TestUtilities",
.product(name: "CustomDump", package: "swift-custom-dump"),
]
),
// Web5 test vectors target
.testTarget(
name: "Web5TestVectors",
dependencies: [
"tbDEX",
"TestUtilities",
.product(name: "CustomDump", package: "swift-custom-dump"),
],
resources: [
.copy("TestVectors/ed25519"),
.copy("TestVectors/secp256k1"),
.copy("TestVectors/did_jwk"),
.copy("Resources/ed25519"),
.copy("Resources/secp256k1"),
.copy("Resources/did_jwk"),
]
),
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

extension Data {
static func fromHexString(_ hexString: String) -> Self? {
public static func fromHexString(_ hexString: String) -> Self? {
var data = Data()
var hex = hexString

Expand All @@ -27,7 +27,7 @@ extension Data {
return data
}

func toHexString() -> String {
public func toHexString() -> String {
return map { String(format: "%02x", $0) }.joined()
}
}
84 changes: 84 additions & 0 deletions Tests/Web5TestVectors/TestVector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import XCTest

/// Representation of a test vector
///
/// This representation uses the following generics:
/// - Input: Type of the file's vector object `input` property. This is unique for each test vector file, and should
/// be defined in the vector's README file.
/// - Output: Type of the file's vector object `output` property. This is unique for each test vector file, and should
/// be defined in the vector's README file.
///
/// [Specification Reference](https://github.com/TBD54566975/sdk-development/tree/main/web5-test-vectors)
struct TestVector<Input: Codable, Output: Codable>: Codable {

/// A general description of the test vectors collection.
private let description: String

/// An array of test vector objects.
private let vectors: [Vector]

/// Default Initializer
/// - Parameters:
/// - fileName: Name of the JSON file containing the test vectors.
/// - subdirectory: Name of subdirectory that contains `fileName`. Used to disambiguate when there are multiple
/// test vector files with the same name.
init(
fileName: String,
subdirectory: String? = nil
) throws {
guard
let url = Bundle.module.url(
forResource: fileName,
withExtension: "json",
subdirectory: subdirectory
)
else {
fatalError("Missing file: \(fileName).json")
}

let data = try Data(contentsOf: url)
self = try JSONDecoder().decode(Self<Input, Output>.self, from: data)
}

/// Run a test vector, individually executing each vector object defined within it.
///
/// - Parameters:
/// - vectorHandler: Closure that will be executed with each vector object defined within the test vector file.
/// This is where you write your assertions!
func run(
vectorHandler: (Vector) throws -> Void
) {
XCTContext.runActivity(named: description) { _ in
for vector in vectors {
do {
try XCTContext.runActivity(named: vector.description) { _ in
try vectorHandler(vector)
}
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
}
}

extension TestVector {

/// Representation of individual vector object that appears within a test vector file
///
/// [Specification Reference](https://github.com/TBD54566975/sdk-development/tree/main/web5-test-vectors)
struct Vector: Codable {

/// A description of what this test vector is testing.
let description: String

/// The input for the test vector, which can be of any type.
let input: Input

/// The expected output for the test vector, which can be of any type.
let output: Output

/// Indicates whether the test vector is expected to produce an error.
let errors: Bool?
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import XCTest
final class Web5TestVectorsDidJwk: XCTestCase {

func test_resolve() throws {
let testVector: TestVector<String, DidResolution.Result> = try loadTestVector(
let testVector = try TestVector<String, DidResolution.Result>(
fileName: "resolve",
subdirectory: "did_jwk"
)

for vector in testVector.vectors {
testVector.run { vector in
let didUri = vector.input
let result = DidJwk.resolve(didUri: didUri)
XCTAssertNoDifference(result, vector.output)
Expand Down
161 changes: 161 additions & 0 deletions Tests/Web5TestVectors/Web5TestVectorsEd25519.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import CustomDump
import TestUtilities
import XCTest

@testable import tbDEX

final class Web5TestVectorsEd25519: XCTestCase {

func test_bytesToPrivateKey() throws {
/// Input data format for `bytes-to-private-key` test vectors
struct Input: Codable {
let privateKeyBytes: String
}

let testVector = try TestVector<Input, Jwk>(
fileName: "bytes-to-private-key",
subdirectory: "ed25519"
)

testVector.run { vector in
let privateKeyBytes = try XCTUnwrap(Data.fromHexString(vector.input.privateKeyBytes))
let privateKey = try Ed25519.shared.bytesToPrivateKey(privateKeyBytes)
XCTAssertNoDifference(privateKey, vector.output)
}
}

func test_bytesToPublicKey() throws {
/// Input data format for `bytes-to-public-key` test vectors
struct Input: Codable {
let publicKeyBytes: String
}

let testVector = try TestVector<Input, Jwk>(
fileName: "bytes-to-public-key",
subdirectory: "ed25519"
)

testVector.run { vector in
let publicKeyBytes = try XCTUnwrap(Data.fromHexString(vector.input.publicKeyBytes))
let publicKey = try Ed25519.shared.bytesToPublicKey(publicKeyBytes)
XCTAssertNoDifference(publicKey, vector.output)
}
}

func test_computePublicKey() throws {
/// Input data format for `compute-public-key` test vectors
struct Input: Codable {
let privateKey: Jwk
}

let testVector = try TestVector<Input, Jwk>(
fileName: "compute-public-key",
subdirectory: "ed25519"
)

testVector.run { vector in
let publicKey = try Ed25519.shared.computePublicKey(privateKey: vector.input.privateKey)
XCTAssertNoDifference(publicKey, vector.output)
}
}

func test_privateKeyToBytes() throws {
/// Input data format for `private-key-to-bytes` test vectors
struct Input: Codable {
let privateKey: Jwk
}

let testVector = try TestVector<Input, String>(
fileName: "private-key-to-bytes",
subdirectory: "ed25519"
)

testVector.run { vector in
let privateKeyBytes = try Ed25519.shared.privateKeyToBytes(vector.input.privateKey)
XCTAssertNoDifference(privateKeyBytes, try XCTUnwrap(Data.fromHexString(vector.output)))
}
}

func test_publicKeyToBytes() throws {
/// Input data format for `public-key-to-bytes` test vectors
struct Input: Codable {
let publicKey: Jwk
}

let testVector = try TestVector<Input, String>(
fileName: "public-key-to-bytes",
subdirectory: "ed25519"
)

testVector.run { vector in
let publicKeyBytes = try Ed25519.shared.publicKeyToBytes(vector.input.publicKey)
XCTAssertNoDifference(publicKeyBytes, try XCTUnwrap(Data.fromHexString(vector.output)))
}
}

func test_sign() throws {
/// Input data format for `sign` test vectors
struct Input: Codable {
let data: String
let key: Jwk
}

let testVector = try TestVector<Input, String>(
fileName: "sign",
subdirectory: "ed25519"
)

testVector.run { vector in
let signature = try Ed25519.shared.sign(
privateKey: vector.input.key,
payload: try XCTUnwrap(Data.fromHexString(vector.input.data))
)

// Apple's Ed25519 implementation employs randomization to generate different signatures
// on every call, even for the same data and key, to guard against side-channel attacks.
// https://developer.apple.com/documentation/cryptokit/curve25519/signing/privatekey/signature(for:)
//
// Because of this, the signature we just generated will NOT be the same as the vector's output,
// but both will be valid signatures.
let isVectorOutputSignatureValid = try Ed25519.shared.verify(
publicKey: try Ed25519.shared.computePublicKey(privateKey: vector.input.key),
signature: try XCTUnwrap(Data.fromHexString(vector.output)),
signedPayload: try XCTUnwrap(Data.fromHexString(vector.input.data))
)

let isGeneratedSignatureValid = try Ed25519.shared.verify(
publicKey: try Ed25519.shared.computePublicKey(privateKey: vector.input.key),
signature: signature,
signedPayload: try XCTUnwrap(Data.fromHexString(vector.input.data))
)

XCTAssertTrue(isVectorOutputSignatureValid)
XCTAssertTrue(isGeneratedSignatureValid)
XCTAssertNotEqual(signature.toHexString(), vector.output)
}
}

func test_verify() throws {
/// Input data format for `verify` test vectors
struct Input: Codable {
let data: String
let key: Jwk
let signature: String
}

let testVector = try TestVector<Input, Bool>(
fileName: "verify",
subdirectory: "ed25519"
)

testVector.run { vector in
let isValid = try Ed25519.shared.verify(
publicKey: vector.input.key,
signature: try XCTUnwrap(Data.fromHexString(vector.input.signature)),
signedPayload: try XCTUnwrap(Data.fromHexString(vector.input.data))
)
XCTAssertNoDifference(isValid, vector.output)
}
}

}
Loading

0 comments on commit 78d57fa

Please sign in to comment.