Update to LibSignal v0.95.0

This commit is contained in:
Max Radermacher 2026-06-09 14:03:50 -05:00
parent 1a4acf84fc
commit 5e81462d83
20 changed files with 9688 additions and 8223 deletions

View File

@ -5,6 +5,11 @@
// WARNING: this file was automatically generated
// swiftlint:disable superfluous_disable_command
// swiftlint and swift-format disagree on some comma formatting
// swiftlint:disable comma
// swiftlint:disable large_tuple
import Foundation
import SignalFfi

View File

@ -20,9 +20,12 @@ internal protocol NiceArgConverter {
}
extension NiceArgConverter {
static func convertArgBorrowed<Result>(_ arg: NiceArg, _ thunk: (FfiArg) throws -> Result) rethrows -> Result {
static internal func genericArgBorrowed<Result>(
_ arg: NiceArg,
_ thunk: (FfiArg) throws -> Result
) rethrows -> Result {
return try withExtendedLifetime(arg) {
let (ffi, ka) = self.convertArg(arg)
let (ffi, ka) = convertArg(arg)
return try withExtendedLifetime(ka) {
return try thunk(ffi)
}

View File

@ -60,6 +60,7 @@ public class SessionRecord: ClonableHandleOwner<SignalMutPointerSessionRecord> {
}
}
/// - Throws: ``SignalError/sessionNotFound(_:)`` if there is no current session state.
public func remoteRegistrationId() throws -> UInt32 {
return try self.withNativeHandle { nativeHandle in
try invokeFnReturningInteger {

View File

@ -0,0 +1,286 @@
//
// Copyright 2026 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalFfi
/// Client-side state for an in-flight avatar upload credential request.
///
/// This value is not sent over the wire; it is retained by the client between issuing a
/// ``AvatarUploadCredentialRequest`` and receiving the corresponding
/// ``AvatarUploadCredentialResponse``.
public final class AvatarUploadCredentialRequestContext: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_avatar_upload_credential_request_context_check_valid_contents)
}
/// Creates a new request context for `aci`.
///
/// - Parameter aci: The account the credential will be issued for. The issuing server must independently
/// authenticate this ACI.
/// - Parameter zkCredentialKey: The account's long-term Ristretto ZK credential key pair.
/// - Parameter rotationId: The server-chosen avatar slot rotation ID, which the client already received
/// when it set its ZK credential key. It is folded into the commitment; the issuing server
/// verifies the request against its own rotation ID, so this must match the server's value.
public static func create(
aci: Aci,
zkCredentialKey: ZkCredentialKeyPair,
rotationId: UInt64
) -> AvatarUploadCredentialRequestContext {
return failOnError {
self.create(
aci: aci,
zkCredentialKey: zkCredentialKey,
rotationId: rotationId,
randomness: try .generate()
)
}
}
/// Creates a new request context, using a dedicated source of randomness.
///
/// This can be used to make tests deterministic. Prefer ``create(aci:zkCredentialKey:rotationId:)``
/// if the source of randomness doesn't matter.
public static func create(
aci: Aci,
zkCredentialKey: ZkCredentialKeyPair,
rotationId: UInt64,
randomness: Randomness
) -> AvatarUploadCredentialRequestContext {
return failOnError {
try withAllBorrowed(aci, zkCredentialKey, randomness) { aci, key, randomness in
try invokeFnReturningVariableLengthSerialized {
signal_avatar_upload_credential_request_context_new($0, aci, key, rotationId, randomness)
}
}
}
}
/// The request to send to the issuing server.
public func getRequest() -> AvatarUploadCredentialRequest {
return failOnError {
try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningVariableLengthSerialized {
signal_avatar_upload_credential_request_context_get_request($0, contents)
}
}
}
}
/// Verifies the issuing server's response and produces a usable ``AvatarUploadCredential``.
///
/// The issuing server chooses the redemption time and embeds it in `response`. The client
/// doesn't need to predict it; this call confirms only that the credential is usable at
/// `now`, since the verifying server applies the same window (see
/// ``AvatarUploadCredentialPresentation/verify(now:serverParams:)``).
///
/// - Parameter response: The response received from the issuing server.
/// - Parameter now: The client's view of wall-clock time. The response's redemption time must be
/// day-aligned and within the redemption window relative to this.
/// - Parameter serverParams: The public params matching the secret params the issuing server used.
/// - Throws ``SignalError/verificationFailed(_:)`` if the response is not valid for this context.
public func receive(
_ response: AvatarUploadCredentialResponse,
now: Date = Date(),
serverParams: GenericServerPublicParams
) throws -> AvatarUploadCredential {
return try withAllBorrowed(self, response, serverParams) { contents, response, params in
try invokeFnReturningVariableLengthSerialized {
signal_avatar_upload_credential_request_context_receive_response(
$0,
contents,
response,
UInt64(now.timeIntervalSince1970),
params
)
}
}
}
}
/// The request a client sends to the issuing server to obtain an avatar upload credential.
public class AvatarUploadCredentialRequest: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_avatar_upload_credential_request_check_valid_contents)
}
/// Issues an avatar upload credential.
///
/// - Parameter aci: The account this credential is for. The server must independently authenticate the
/// client as this ACI.
/// - Parameter zkCredentialKey: The account's long-term Ristretto ZK credential public key from
/// the server's authoritative store for this account. The request's well-formedness proof
/// binds the blinded commitment to this key, so passing the wrong value will fail issuance.
/// - Parameter rotationId: The server-chosen avatar slot rotation ID, incorporated into the commitment.
/// The server must return this value to the client (it is carried in the response) so the
/// client can compute the full commitment.
/// - Parameter redemptionTime: Must be a round number of days since the Unix epoch.
/// - Parameter serverParams: The params that will be used by the verifying server to verify this credential.
/// - Throws ``SignalError/verificationFailed(_:)`` if the request is not well-formed for `aci` and
/// `zkCredentialKey`.
public func issueCredential(
aci: Aci,
zkCredentialKey: ZkCredentialPublicKey,
rotationId: UInt64,
redemptionTime: Date,
serverParams: GenericServerSecretParams
) throws -> AvatarUploadCredentialResponse {
return try self.issueCredential(
aci: aci,
zkCredentialKey: zkCredentialKey,
rotationId: rotationId,
redemptionTime: redemptionTime,
serverParams: serverParams,
randomness: try .generate()
)
}
/// Issues an avatar upload credential, using a dedicated source of randomness.
///
/// This can be used to make tests deterministic. Prefer ``issueCredential(aci:zkcredentialKey:rotationId:redemptionTime:serverParams:)``
/// if the source of randomness doesn't matter.
public func issueCredential(
aci: Aci,
zkCredentialKey: ZkCredentialPublicKey,
rotationId: UInt64,
redemptionTime: Date,
serverParams: GenericServerSecretParams,
randomness: Randomness
) throws -> AvatarUploadCredentialResponse {
return try withAllBorrowed(self, aci, zkCredentialKey, serverParams, randomness) {
contents,
aci,
key,
params,
randomness in
try invokeFnReturningVariableLengthSerialized {
signal_avatar_upload_credential_request_issue_deterministic(
$0,
contents,
aci,
key,
rotationId,
UInt64(redemptionTime.timeIntervalSince1970),
params,
randomness
)
}
}
}
}
/// The issuing server's response to an ``AvatarUploadCredentialRequest``.
public class AvatarUploadCredentialResponse: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_avatar_upload_credential_response_check_valid_contents)
}
}
/// A usable avatar upload credential, held by the client after a successful issuance.
///
/// Call ``Self/present(serverParams:)`` to produce an ``AvatarUploadCredentialPresentation`` for a verifying
/// server.
public class AvatarUploadCredential: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_avatar_upload_credential_check_valid_contents)
}
/// Produces a presentation of this credential for a verifying server.
public func present(serverParams: GenericServerPublicParams) -> AvatarUploadCredentialPresentation {
return failOnError {
self.present(serverParams: serverParams, randomness: try .generate())
}
}
/// Produces a presentation of this credential, using a dedicated source of randomness.
///
/// This can be used to make tests deterministic. Prefer ``present(serverParams:)``
/// if the source of randomness doesn't matter.
public func present(
serverParams: GenericServerPublicParams,
randomness: Randomness
) -> AvatarUploadCredentialPresentation {
return failOnError {
try withAllBorrowed(self, serverParams, randomness) { contents, serverParams, randomness in
try invokeFnReturningVariableLengthSerialized {
signal_avatar_upload_credential_present_deterministic($0, contents, serverParams, randomness)
}
}
}
}
/// The 32-byte commitment `Cm` (the avatar slot identifier).
///
/// This is a Pedersen commitment, not a key, so it carries no type-tag prefix.
public var commitment: Data {
failOnError {
try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningFixedLengthArray {
signal_avatar_upload_credential_get_cm($0, contents)
}
}
}
}
/// The redemption time the issuing server chose for this credential.
public var redemptionTime: Date {
let secondsSinceEpoch = failOnError {
try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningInteger {
signal_avatar_upload_credential_get_redemption_time($0, contents)
}
}
}
return Date(timeIntervalSince1970: TimeInterval(secondsSinceEpoch))
}
}
/// A presentation of an ``AvatarUploadCredential``, sent to a verifying server.
public class AvatarUploadCredentialPresentation: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_avatar_upload_credential_presentation_check_valid_contents)
}
/// Verifies the presentation against the (given) current time.
///
/// - Throws: ``SignalError/verificationFailed(_:)`` if the presentation is invalid or outside its redemption
/// window.
public func verify(now: Date = Date(), serverParams: GenericServerSecretParams) throws {
try withAllBorrowed(self, serverParams) { contents, serverParams in
try checkError(
signal_avatar_upload_credential_presentation_verify(
contents,
UInt64(now.timeIntervalSince1970),
serverParams
)
)
}
}
/// The 32-byte commitment `Cm` (the avatar slot identifier) revealed by this presentation.
///
/// This is a Pedersen commitment, not a key, so it carries no type-tag prefix.
public var commitment: Data {
failOnError {
try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningFixedLengthArray {
signal_avatar_upload_credential_presentation_get_cm($0, contents)
}
}
}
}
/// The redemption time the issuing server chose for this credential.
public var redemptionTime: Date {
let secondsSinceEpoch = failOnError {
try withUnsafeBorrowedBuffer { contents in
try invokeFnReturningInteger {
signal_avatar_upload_credential_presentation_get_redemption_time($0, contents)
}
}
}
return Date(timeIntervalSince1970: TimeInterval(secondsSinceEpoch))
}
}

View File

@ -0,0 +1,54 @@
//
// Copyright 2026 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalFfi
/// A long-term Ristretto ZK credential key pair owned by an account.
///
/// Distinct from the account's curve25519 identity key. Used as a binding identity across ZK
/// credentials issued to the account (currently the avatar upload credential).
///
/// The secret half must be persisted by the account holder and synced to linked devices. The
/// public half is uploaded to the server.
public class ZkCredentialKeyPair: ByteArray {
public static func generate() -> ZkCredentialKeyPair {
return failOnError {
self.generate(randomness: try Randomness.generate())
}
}
public static func generate(randomness: Randomness) -> ZkCredentialKeyPair {
return failOnError {
try randomness.withUnsafePointerToBytes { randomness in
try invokeFnReturningVariableLengthSerialized {
signal_zk_credential_key_pair_generate_deterministic($0, randomness)
}
}
}
}
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_zk_credential_key_pair_check_valid_contents)
}
public var publicKey: ZkCredentialPublicKey {
failOnError {
try withUnsafeBorrowedBuffer { keyPairBytes in
try invokeFnReturningVariableLengthSerialized {
signal_zk_credential_key_pair_get_public_key($0, keyPairBytes)
}
}
}
}
}
/// The public half of a ``ZkCredentialKeyPair``.
public class ZkCredentialPublicKey: ByteArray {
public required init(contents: Data) throws {
try super.init(contents, checkValid: signal_zk_credential_public_key_check_valid_contents)
}
}

View File

@ -1805,6 +1805,36 @@ SignalFfiError *signal_authenticated_chat_connection_send_raw_grpc(SignalCPromis
SignalFfiError *signal_authenticated_chat_connection_send_sync_message(SignalCPromisebool *promise, SignalConstPointerTokioAsyncContext async_runtime, SignalConstPointerAuthenticatedChatConnection chat, uint64_t timestamp, SignalBorrowedSliceOfu32 device_ids, SignalBorrowedSliceOfu32 registration_ids, SignalBorrowedSliceOfConstPointerCiphertextMessage contents, bool is_urgent);
SignalFfiError *signal_avatar_upload_credential_check_valid_contents(SignalBorrowedBuffer credential_bytes);
SignalFfiError *signal_avatar_upload_credential_get_cm(uint8_t (*out)[32], SignalBorrowedBuffer credential_bytes);
SignalFfiError *signal_avatar_upload_credential_get_redemption_time(uint64_t *out, SignalBorrowedBuffer credential_bytes);
SignalFfiError *signal_avatar_upload_credential_present_deterministic(SignalOwnedBuffer *out, SignalBorrowedBuffer credential_bytes, SignalBorrowedBuffer server_params_bytes, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_avatar_upload_credential_presentation_check_valid_contents(SignalBorrowedBuffer presentation_bytes);
SignalFfiError *signal_avatar_upload_credential_presentation_get_cm(uint8_t (*out)[32], SignalBorrowedBuffer presentation_bytes);
SignalFfiError *signal_avatar_upload_credential_presentation_get_redemption_time(uint64_t *out, SignalBorrowedBuffer presentation_bytes);
SignalFfiError *signal_avatar_upload_credential_presentation_verify(SignalBorrowedBuffer presentation_bytes, uint64_t current_time, SignalBorrowedBuffer server_params_bytes);
SignalFfiError *signal_avatar_upload_credential_request_check_valid_contents(SignalBorrowedBuffer request_bytes);
SignalFfiError *signal_avatar_upload_credential_request_context_check_valid_contents(SignalBorrowedBuffer context_bytes);
SignalFfiError *signal_avatar_upload_credential_request_context_get_request(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes);
SignalFfiError *signal_avatar_upload_credential_request_context_new(SignalOwnedBuffer *out, const SignalServiceIdFixedWidthBinaryBytes *aci, SignalBorrowedBuffer zk_credential_key_pair_bytes, uint64_t rotation_id, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_avatar_upload_credential_request_context_receive_response(SignalOwnedBuffer *out, SignalBorrowedBuffer context_bytes, SignalBorrowedBuffer response_bytes, uint64_t current_time, SignalBorrowedBuffer params_bytes);
SignalFfiError *signal_avatar_upload_credential_request_issue_deterministic(SignalOwnedBuffer *out, SignalBorrowedBuffer request_bytes, const SignalServiceIdFixedWidthBinaryBytes *aci, SignalBorrowedBuffer zk_credential_key_pub_bytes, uint64_t rotation_id, uint64_t redemption_time, SignalBorrowedBuffer params_bytes, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_avatar_upload_credential_response_check_valid_contents(SignalBorrowedBuffer response_bytes);
SignalFfiError *signal_backup_auth_credential_check_valid_contents(SignalBorrowedBuffer params_bytes);
SignalFfiError *signal_backup_auth_credential_get_backup_id(uint8_t (*out)[16], SignalBorrowedBuffer credential_bytes);
@ -2910,4 +2940,12 @@ SignalFfiError *signal_validating_mac_update(int32_t *out, SignalMutPointerValid
SignalFfiError *signal_webp_sanitizer_sanitize(SignalConstPointerFfiSyncInputStreamStruct input);
SignalFfiError *signal_zk_credential_key_pair_check_valid_contents(SignalBorrowedBuffer key_pair_bytes);
SignalFfiError *signal_zk_credential_key_pair_generate_deterministic(SignalOwnedBuffer *out, const uint8_t (*randomness)[SignalRANDOMNESS_LEN]);
SignalFfiError *signal_zk_credential_key_pair_get_public_key(SignalOwnedBuffer *out, SignalBorrowedBuffer key_pair_bytes);
SignalFfiError *signal_zk_credential_public_key_check_valid_contents(SignalBorrowedBuffer public_key_bytes);
#endif /* SIGNAL_FFI_H_ */

View File

@ -240,6 +240,112 @@ typedef struct {
SignalTestingIntBox *raw;
} SignalMutPointerTestingIntBox;
typedef struct {
int32_t _0;
int32_t _1;
} SignalMyTestPointFfiResult;
typedef struct {
int32_t my_numeric_field;
SignalCStringPtr my_string_field;
} SignalMyTestStructFfiResult;
typedef enum {
SignalMyTestEnumFfiResultUnit,
SignalMyTestEnumFfiResultSingle,
SignalMyTestEnumFfiResultSingleNamed,
SignalMyTestEnumFfiResultDouble,
SignalMyTestEnumFfiResultRecord,
} SignalMyTestEnumFfiResult_Tag;
typedef struct {
} SignalMyTestEnumFfiResultSignalUnit_Body;
typedef struct {
int32_t _0;
} SignalMyTestEnumFfiResultSignalSingle_Body;
typedef struct {
int32_t x;
} SignalMyTestEnumFfiResultSignalSingleNamed_Body;
typedef struct {
int32_t _0;
int32_t _1;
} SignalMyTestEnumFfiResultSignalDouble_Body;
typedef struct {
SignalCStringPtr person_name;
int32_t person_age;
SignalMyTestPointFfiResult position;
SignalMyTestStructFfiResult fun_struct;
} SignalMyTestEnumFfiResultSignalRecord_Body;
typedef struct {
SignalMyTestEnumFfiResult_Tag tag;
union {
SignalMyTestEnumFfiResultSignalUnit_Body unit;
SignalMyTestEnumFfiResultSignalSingle_Body single;
SignalMyTestEnumFfiResultSignalSingleNamed_Body single_named;
SignalMyTestEnumFfiResultSignalDouble_Body double_;
SignalMyTestEnumFfiResultSignalRecord_Body record;
};
} SignalMyTestEnumFfiResult;
typedef struct {
int32_t _0;
int32_t _1;
} SignalMyTestPointFfiArg;
typedef struct {
int32_t my_numeric_field;
const char *my_string_field;
} SignalMyTestStructFfiArg;
typedef enum {
SignalMyTestEnumFfiArgUnit,
SignalMyTestEnumFfiArgSingle,
SignalMyTestEnumFfiArgSingleNamed,
SignalMyTestEnumFfiArgDouble,
SignalMyTestEnumFfiArgRecord,
} SignalMyTestEnumFfiArg_Tag;
typedef struct {
} SignalMyTestEnumFfiArgSignalUnit_Body;
typedef struct {
int32_t _0;
} SignalMyTestEnumFfiArgSignalSingle_Body;
typedef struct {
int32_t x;
} SignalMyTestEnumFfiArgSignalSingleNamed_Body;
typedef struct {
int32_t _0;
int32_t _1;
} SignalMyTestEnumFfiArgSignalDouble_Body;
typedef struct {
const char *person_name;
int32_t person_age;
SignalMyTestPointFfiArg position;
SignalMyTestStructFfiArg fun_struct;
} SignalMyTestEnumFfiArgSignalRecord_Body;
typedef struct {
SignalMyTestEnumFfiArg_Tag tag;
union {
SignalMyTestEnumFfiArgSignalUnit_Body unit;
SignalMyTestEnumFfiArgSignalSingle_Body single;
SignalMyTestEnumFfiArgSignalSingleNamed_Body single_named;
SignalMyTestEnumFfiArgSignalDouble_Body double_;
SignalMyTestEnumFfiArgSignalRecord_Body record;
};
} SignalMyTestEnumFfiArg;
typedef struct {
int32_t first;
SignalCStringPtr second;
@ -435,6 +541,18 @@ SignalFfiError *signal_testing_key_trans_non_fatal_verification_failure(void);
SignalFfiError *signal_testing_key_trans_stored_account_data(SignalOwnedBuffer *out);
SignalFfiError *signal_testing_my_test_enum_identity(SignalMyTestEnumFfiResult *out, SignalMyTestEnumFfiArg x);
SignalFfiError *signal_testing_my_test_enum_to_string(SignalCStringPtr *out, SignalMyTestEnumFfiArg x);
SignalFfiError *signal_testing_my_test_point_identity(SignalMyTestPointFfiResult *out, SignalMyTestPointFfiArg x);
SignalFfiError *signal_testing_my_test_point_to_string(SignalCStringPtr *out, SignalMyTestPointFfiArg x);
SignalFfiError *signal_testing_my_test_struct_identity(SignalMyTestStructFfiResult *out, SignalMyTestStructFfiArg x);
SignalFfiError *signal_testing_my_test_struct_to_string(SignalCStringPtr *out, SignalMyTestStructFfiArg x);
SignalFfiError *signal_testing_other_testing_handle_type_get_value(SignalCStringPtr *out, SignalConstPointerOtherTestingHandleType handle);
SignalFfiError *signal_testing_panic_in_body_async(const void *_input);

View File

@ -0,0 +1,232 @@
//
// Copyright 2026 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
import Testing
private struct AvatarUploadCredentialTests {
// Chosen randomly.
let TEST_ACI = try! Aci.parseFrom(serviceIdString: "c0fc16e4-bae5-4343-9f0d-e7ecf4251343")
let ZK_CRED_KEY_RANDOM = Randomness(
fromHexString: "4242424242424242424242424242424242424242424242424242424242424242"
)!
let WRONG_ZK_CRED_KEY_RANDOM = Randomness(
fromHexString: "9999999999999999999999999999999999999999999999999999999999999999"
)!
let SERVER_SECRET_RANDOM = Randomness(
fromHexString: "6987b92bdea075d3f8b42b39d780a5be0bc264874a18e11cac694e4fe28f6cca"
)!
let CREATE_RANDOM = Randomness(
fromHexString: "657e7a2ac9dd981b789c9b2fbcdfbbe46cb6230c7a2c67c1be3472cb006463e2"
)!
let ISSUE_RANDOM = Randomness(
fromHexString: "8e3f24cb0a7e7614c7b4ab04ba8a145f108c53c4b10a096aa4503ae1e0c9f661"
)!
let PRESENT_RANDOM = Randomness(
fromHexString: "475149b2bdcb6f9bd3a8e3a5d4c6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8"
)!
let ROTATION_ID: UInt64 = 7
@Test
func testAvatarUploadCredentialIntegration() {
// SERVER: generate keys.
let serverSecretParams = GenericServerSecretParams.generate(randomness: SERVER_SECRET_RANDOM)
let serverPublicParams = serverSecretParams.getPublicParams()
// CLIENT: generate its long-term ZK credential key pair and (out of band) register the public
// half with the server.
let zkCredentialKeyPair = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
let zkCredentialKeyPublic = zkCredentialKeyPair.publicKey
// CLIENT: build a request.
let context = AvatarUploadCredentialRequestContext.create(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPair,
rotationId: ROTATION_ID,
randomness: CREATE_RANDOM
)
let request = context.getRequest()
// Round-tripping the request through serialize() must preserve it.
#expect(request.serialize() == (try! AvatarUploadCredentialRequest(contents: request.serialize()).serialize()))
// SERVER: authenticate the ACI, look up its ZK credential key, and issue.
let now = Date()
let nowInSeconds = UInt64(now.timeIntervalSince1970)
let startOfDayInSeconds = nowInSeconds - (nowInSeconds % SECONDS_PER_DAY)
let startOfDay = Date(timeIntervalSince1970: TimeInterval(startOfDayInSeconds))
let response = try! request.issueCredential(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPublic,
rotationId: ROTATION_ID,
redemptionTime: startOfDay,
serverParams: serverSecretParams,
randomness: ISSUE_RANDOM
)
// CLIENT: verify and unblind the credential. The client passes its current wall-clock time;
// libsignal checks that the credential's redemption_time (chosen by the server, carried in
// `response`) is day-aligned and inside the redemption window relative to `now`.
let credential = try! context.receive(response, now: now, serverParams: serverPublicParams)
// The client can read back the redemption time the issuing server chose.
#expect(startOfDay == credential.redemptionTime)
let credentialDefaultTime = try! context.receive(response, serverParams: serverPublicParams)
#expect(credential.serialize() == credentialDefaultTime.serialize())
// CLIENT: present the credential.
let presentation = credential.present(serverParams: serverPublicParams, randomness: PRESENT_RANDOM)
// The revealed commitment Cm must match between the credential and its presentation.
#expect(credential.commitment == presentation.commitment)
#expect(credential.redemptionTime == presentation.redemptionTime)
// SERVER: verify the presentation across the redemption window.
try! presentation.verify(now: startOfDay, serverParams: serverSecretParams)
try! presentation.verify(now: startOfDay + TimeInterval(SECONDS_PER_DAY), serverParams: serverSecretParams)
#expect("Credential should be expired more than 2 days after redemption time") {
try presentation.verify(
now: startOfDay + 2 * TimeInterval(SECONDS_PER_DAY + 1),
serverParams: serverSecretParams
)
} throws: {
if case SignalError.verificationFailed(_:) = $0 { true } else { false }
}
#expect("Credential should be invalid before its redemption time") {
try presentation.verify(
now: startOfDay - TimeInterval(SECONDS_PER_DAY + 1),
serverParams: serverSecretParams
)
} throws: {
if case SignalError.verificationFailed(_:) = $0 { true } else { false }
}
}
@Test
func testIssuanceRejectsWrongAci() {
let serverSecretParams = GenericServerSecretParams.generate(randomness: SERVER_SECRET_RANDOM)
let zkCredentialKeyPair = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
let zkCredentialKeyPublic = zkCredentialKeyPair.publicKey
let context = AvatarUploadCredentialRequestContext.create(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPair,
rotationId: ROTATION_ID,
randomness: CREATE_RANDOM
)
let request = context.getRequest()
let wrongAci = try! Aci.parseFrom(serviceIdString: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
let now = Date()
let nowInSeconds = UInt64(now.timeIntervalSince1970)
let startOfDayInSeconds = nowInSeconds - (nowInSeconds % SECONDS_PER_DAY)
let startOfDay = Date(timeIntervalSince1970: TimeInterval(startOfDayInSeconds))
#expect("Issuance should fail when the server checks against a different ACI") {
try request.issueCredential(
aci: wrongAci,
zkCredentialKey: zkCredentialKeyPublic,
rotationId: ROTATION_ID,
redemptionTime: startOfDay,
serverParams: serverSecretParams,
randomness: ISSUE_RANDOM
)
} throws: {
if case SignalError.verificationFailed(_:) = $0 { true } else { false }
}
}
@Test
func testIssuanceRejectsWrongZkCredentialKey() {
let serverSecretParams = GenericServerSecretParams.generate(randomness: SERVER_SECRET_RANDOM)
let zkCredentialKeyPair = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
let context = AvatarUploadCredentialRequestContext.create(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPair,
rotationId: ROTATION_ID,
randomness: CREATE_RANDOM
)
let request = context.getRequest()
// Server has a different ZK credential public key on file for this account.
let wrongZkCredentialKeyPublic = ZkCredentialKeyPair.generate(randomness: WRONG_ZK_CRED_KEY_RANDOM).publicKey
let now = Date()
let nowInSeconds = UInt64(now.timeIntervalSince1970)
let startOfDayInSeconds = nowInSeconds - (nowInSeconds % SECONDS_PER_DAY)
let startOfDay = Date(timeIntervalSince1970: TimeInterval(startOfDayInSeconds))
#expect("Issuance should fail when the server uses a different ZK credential public key") {
try request.issueCredential(
aci: TEST_ACI,
zkCredentialKey: wrongZkCredentialKeyPublic,
rotationId: ROTATION_ID,
redemptionTime: startOfDay,
serverParams: serverSecretParams,
randomness: ISSUE_RANDOM
)
} throws: {
if case SignalError.verificationFailed(_:) = $0 { true } else { false }
}
}
@Test
func testIssuanceRejectsWrongRotationId() {
let serverSecretParams = GenericServerSecretParams.generate(randomness: SERVER_SECRET_RANDOM)
let zkCredentialKeyPair = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
let zkCredentialKeyPublic = zkCredentialKeyPair.publicKey
// Client commits to one rotation ID...
let context = AvatarUploadCredentialRequestContext.create(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPair,
rotationId: ROTATION_ID,
randomness: CREATE_RANDOM
)
let request = context.getRequest()
let now = Date()
let nowInSeconds = UInt64(now.timeIntervalSince1970)
let startOfDayInSeconds = nowInSeconds - (nowInSeconds % SECONDS_PER_DAY)
let startOfDay = Date(timeIntervalSince1970: TimeInterval(startOfDayInSeconds))
#expect("Issuance should fail when the server uses a different rotation ID") {
try request.issueCredential(
aci: TEST_ACI,
zkCredentialKey: zkCredentialKeyPublic,
rotationId: ROTATION_ID + 1,
redemptionTime: startOfDay,
serverParams: serverSecretParams,
randomness: ISSUE_RANDOM
)
} throws: {
if case SignalError.verificationFailed(_:) = $0 { true } else { false }
}
}
@Test
func testPublicKeyDerivationIsDeterministic() {
let a = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
let b = ZkCredentialKeyPair.generate(randomness: ZK_CRED_KEY_RANDOM)
#expect(a.serialize() == b.serialize())
#expect(a.publicKey.serialize() == b.publicKey.serialize())
}
}

View File

@ -5,13 +5,622 @@
// WARNING: this file was automatically generated
// swiftlint:disable superfluous_disable_command
// swiftlint and swift-format disagree on some comma formatting
// swiftlint:disable comma
// swiftlint:disable large_tuple
#if !os(iOS) || targetEnvironment(simulator)
import Foundation
import SignalFfi
@testable import LibSignalClient
internal enum MyTestEnum {
case unit
case single(Int32)
case singleNamed(x: Int32)
case double(Int32, Int32)
case record(personName: String, personAge: Int32, position: MyTestPoint, funStruct: MyTestStruct)
}
internal struct MyTestPoint {
var _0: Int32
var _1: Int32
init(_ _0: Int32, _ _1: Int32, ) {
self._0 = _0
self._1 = _1
}
init(_0: Int32, _1: Int32, ) {
self._0 = _0
self._1 = _1
}
}
internal struct MyTestStruct {
var myNumericField: Int32
var myStringField: String
}
internal enum DerivedReturnConverterMyTestEnum: NiceReturnConverter {
typealias NiceReturn = MyTestEnum
typealias FfiReturn = SignalMyTestEnumFfiResult
static func emptyFfiReturn() -> FfiReturn {
SignalMyTestEnumFfiResult()
}
static func convertReturn(consuming ffiValue: FfiReturn) throws -> NiceReturn {
switch ffiValue.tag {
case SignalMyTestEnumFfiResultUnit:
return MyTestEnum.unit
case SignalMyTestEnumFfiResultSingle:
let _0 = Result {
try IdentityConverter<Int32>.convertReturn(
consuming: ffiValue.single._0
)
}
return MyTestEnum.single(try _0.get())
case SignalMyTestEnumFfiResultSingleNamed:
let x = Result {
try IdentityConverter<Int32>.convertReturn(
consuming: ffiValue.single_named.x
)
}
return MyTestEnum.singleNamed(x: try x.get())
case SignalMyTestEnumFfiResultDouble:
let _0 = Result {
try IdentityConverter<Int32>.convertReturn(
consuming: ffiValue.double_._0
)
}
let _1 = Result {
try IdentityConverter<Int32>.convertReturn(
consuming: ffiValue.double_._1
)
}
return MyTestEnum.double(try _0.get(), try _1.get())
case SignalMyTestEnumFfiResultRecord:
let person_name = Result {
try StringConverter.convertReturn(
consuming: ffiValue.record.person_name
)
}
let person_age = Result {
try IdentityConverter<Int32>.convertReturn(
consuming: ffiValue.record.person_age
)
}
let position = Result {
try DerivedReturnConverterMyTestPoint.convertReturn(
consuming: ffiValue.record.position
)
}
let fun_struct = Result {
try DerivedReturnConverterMyTestStruct.convertReturn(
consuming: ffiValue.record.fun_struct
)
}
return MyTestEnum.record(
personName: try person_name.get(),
personAge: try person_age.get(),
position: try position.get(),
funStruct: try fun_struct.get()
)
default:
throw SignalError.internalError("Unexpected enum tag for MyTestEnum: \(ffiValue.tag)")
}
}
}
internal enum DerivedReturnConverterMyTestPoint: NiceReturnConverter {
typealias NiceReturn = MyTestPoint
typealias FfiReturn = SignalMyTestPointFfiResult
static func emptyFfiReturn() -> FfiReturn {
SignalMyTestPointFfiResult()
}
static func convertReturn(consuming ffiValue: FfiReturn) throws -> NiceReturn {
let _0 = Result { try IdentityConverter<Int32>.convertReturn(consuming: ffiValue._0) }
let _1 = Result { try IdentityConverter<Int32>.convertReturn(consuming: ffiValue._1) }
return MyTestPoint(_0: try _0.get(), _1: try _1.get())
}
}
internal enum DerivedReturnConverterMyTestStruct: NiceReturnConverter {
typealias NiceReturn = MyTestStruct
typealias FfiReturn = SignalMyTestStructFfiResult
static func emptyFfiReturn() -> FfiReturn {
SignalMyTestStructFfiResult()
}
static func convertReturn(consuming ffiValue: FfiReturn) throws -> NiceReturn {
let my_numeric_field = Result {
try IdentityConverter<Int32>.convertReturn(consuming: ffiValue.my_numeric_field)
}
let my_string_field = Result { try StringConverter.convertReturn(consuming: ffiValue.my_string_field) }
return MyTestStruct(myNumericField: try my_numeric_field.get(), myStringField: try my_string_field.get())
}
}
internal enum MyTestEnumArgConverterKeepAlive {
case unit(())
case single((IdentityConverter<Int32>.KeepAlive?))
case singleNamed((IdentityConverter<Int32>.KeepAlive?))
case double((IdentityConverter<Int32>.KeepAlive?, IdentityConverter<Int32>.KeepAlive?))
case record(
(
StringConverter.KeepAlive?, IdentityConverter<Int32>.KeepAlive?, DerivedArgConverterMyTestPoint.KeepAlive?,
DerivedArgConverterMyTestStruct.KeepAlive?
)
)
}
internal enum DerivedArgConverterMyTestEnum: NiceArgConverter {
typealias NiceArg = MyTestEnum
typealias FfiArg = SignalMyTestEnumFfiArg
typealias KeepAlive = MyTestEnumArgConverterKeepAlive
static func convertArg(_ niceArg: NiceArg) -> (FfiArg, KeepAlive?) {
switch niceArg {
case .unit:
let ffiStructArg = SignalMyTestEnumFfiArgSignalUnit_Body()
let ffiStructKeepAlive: ()? =
(false) ? () : nil
return (
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgUnit,
.init(unit: ffiStructArg),
),
ffiStructKeepAlive.map { .unit($0) },
)
case .single(
let _0,
):
let (_0_ffi, _0_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(_0)
let ffiStructArg = SignalMyTestEnumFfiArgSignalSingle_Body(_0: _0_ffi, )
let ffiStructKeepAlive: (IdentityConverter<Int32>.KeepAlive?, )? =
(_0_keepalive != nil || false) ? (_0_keepalive,) : nil
return (
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgSingle,
.init(single: ffiStructArg),
),
ffiStructKeepAlive.map { .single($0) },
)
case .singleNamed(
let x,
):
let (x_ffi, x_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(x)
let ffiStructArg = SignalMyTestEnumFfiArgSignalSingleNamed_Body(x: x_ffi, )
let ffiStructKeepAlive: (IdentityConverter<Int32>.KeepAlive?, )? =
(x_keepalive != nil || false) ? (x_keepalive,) : nil
return (
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgSingleNamed,
.init(single_named: ffiStructArg),
),
ffiStructKeepAlive.map { .singleNamed($0) },
)
case .double(
let _0,
let _1,
):
let (_0_ffi, _0_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(_0)
let (_1_ffi, _1_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(_1)
let ffiStructArg = SignalMyTestEnumFfiArgSignalDouble_Body(_0: _0_ffi, _1: _1_ffi, )
let ffiStructKeepAlive: (IdentityConverter<Int32>.KeepAlive?, IdentityConverter<Int32>.KeepAlive?, )? =
(_0_keepalive != nil || _1_keepalive != nil || false) ? (_0_keepalive, _1_keepalive,) : nil
return (
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgDouble,
.init(double_: ffiStructArg),
),
ffiStructKeepAlive.map { .double($0) },
)
case .record(
personName: let person_name,
personAge: let person_age,
let position,
funStruct: let fun_struct,
):
let (person_name_ffi, person_name_keepalive):
(
StringConverter.FfiArg,
StringConverter.KeepAlive?,
) = StringConverter.convertArg(person_name)
let (person_age_ffi, person_age_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(person_age)
let (position_ffi, position_keepalive):
(
DerivedArgConverterMyTestPoint.FfiArg,
DerivedArgConverterMyTestPoint.KeepAlive?,
) = DerivedArgConverterMyTestPoint.convertArg(position)
let (fun_struct_ffi, fun_struct_keepalive):
(
DerivedArgConverterMyTestStruct.FfiArg,
DerivedArgConverterMyTestStruct.KeepAlive?,
) = DerivedArgConverterMyTestStruct.convertArg(fun_struct)
let ffiStructArg = SignalMyTestEnumFfiArgSignalRecord_Body(
person_name: person_name_ffi,
person_age: person_age_ffi,
position: position_ffi,
fun_struct: fun_struct_ffi,
)
let ffiStructKeepAlive:
(
StringConverter.KeepAlive?, IdentityConverter<Int32>.KeepAlive?,
DerivedArgConverterMyTestPoint.KeepAlive?, DerivedArgConverterMyTestStruct.KeepAlive?,
)? =
(person_name_keepalive != nil || person_age_keepalive != nil || position_keepalive != nil
|| fun_struct_keepalive != nil || false)
? (person_name_keepalive, person_age_keepalive, position_keepalive, fun_struct_keepalive,) : nil
return (
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgRecord,
.init(record: ffiStructArg),
),
ffiStructKeepAlive.map { .record($0) },
)
}
}
static func convertArgBorrowed<Result>(
_ niceArg: NiceArg,
_ niceThunk: (FfiArg) throws -> Result,
) rethrows -> Result {
switch niceArg {
case .unit:
return try niceThunk(
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgUnit,
.init(
unit:
SignalMyTestEnumFfiArgSignalUnit_Body()
),
)
)
case .single(
let _0,
):
return try IdentityConverter<Int32>.convertArgBorrowed(_0) {
ffi__0 in
return try niceThunk(
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgSingle,
.init(
single:
SignalMyTestEnumFfiArgSignalSingle_Body(
_0: ffi__0,
)
),
)
)
}
case .singleNamed(
let x,
):
return try IdentityConverter<Int32>.convertArgBorrowed(x) {
ffi_x in
return try niceThunk(
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgSingleNamed,
.init(
single_named:
SignalMyTestEnumFfiArgSignalSingleNamed_Body(
x: ffi_x,
)
),
)
)
}
case .double(
let _0,
let _1,
):
return try IdentityConverter<Int32>.convertArgBorrowed(_0) {
ffi__0 in
return try IdentityConverter<Int32>.convertArgBorrowed(_1) {
ffi__1 in
return try niceThunk(
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgDouble,
.init(
double_:
SignalMyTestEnumFfiArgSignalDouble_Body(
_0: ffi__0,
_1: ffi__1,
)
),
)
)
}
}
case .record(
personName: let person_name,
personAge: let person_age,
let position,
funStruct: let fun_struct,
):
return try StringConverter.convertArgBorrowed(person_name) {
ffi_person_name in
return try IdentityConverter<Int32>.convertArgBorrowed(person_age) {
ffi_person_age in
return try DerivedArgConverterMyTestPoint.convertArgBorrowed(position) {
ffi_position in
return try DerivedArgConverterMyTestStruct.convertArgBorrowed(fun_struct) {
ffi_fun_struct in
return try niceThunk(
SignalMyTestEnumFfiArg.init(
tag: SignalMyTestEnumFfiArgRecord,
.init(
record:
SignalMyTestEnumFfiArgSignalRecord_Body(
person_name: ffi_person_name,
person_age: ffi_person_age,
position: ffi_position,
fun_struct: ffi_fun_struct,
)
),
)
)
}
}
}
}
}
}
}
internal enum DerivedArgConverterMyTestPoint: NiceArgConverter {
typealias NiceArg = MyTestPoint
typealias FfiArg = SignalMyTestPointFfiArg
typealias KeepAlive = (IdentityConverter<Int32>.KeepAlive?, IdentityConverter<Int32>.KeepAlive?, )
static func convertArg(_ niceArg: NiceArg) -> (FfiArg, KeepAlive?) {
let _0 = niceArg._0
let _1 = niceArg._1
let (_0_ffi, _0_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(_0)
let (_1_ffi, _1_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(_1)
let ffiStructArg = FfiArg(_0: _0_ffi, _1: _1_ffi, )
let ffiStructKeepAlive: (IdentityConverter<Int32>.KeepAlive?, IdentityConverter<Int32>.KeepAlive?, )? =
(_0_keepalive != nil || _1_keepalive != nil || false) ? (_0_keepalive, _1_keepalive,) : nil
return (ffiStructArg, ffiStructKeepAlive)
}
static func convertArgBorrowed<Result>(
_ niceArg: NiceArg,
_ niceThunk: (FfiArg) throws -> Result,
) rethrows -> Result {
let _0 = niceArg._0
let _1 = niceArg._1
return try IdentityConverter<Int32>.convertArgBorrowed(_0) {
ffi__0 in
return try IdentityConverter<Int32>.convertArgBorrowed(_1) {
ffi__1 in
return try niceThunk(
FfiArg(
_0: ffi__0,
_1: ffi__1,
)
)
}
}
}
}
internal enum DerivedArgConverterMyTestStruct: NiceArgConverter {
typealias NiceArg = MyTestStruct
typealias FfiArg = SignalMyTestStructFfiArg
typealias KeepAlive = (IdentityConverter<Int32>.KeepAlive?, StringConverter.KeepAlive?, )
static func convertArg(_ niceArg: NiceArg) -> (FfiArg, KeepAlive?) {
let my_numeric_field = niceArg.myNumericField
let my_string_field = niceArg.myStringField
let (my_numeric_field_ffi, my_numeric_field_keepalive):
(
IdentityConverter<Int32>.FfiArg,
IdentityConverter<Int32>.KeepAlive?,
) = IdentityConverter<Int32>.convertArg(my_numeric_field)
let (my_string_field_ffi, my_string_field_keepalive):
(
StringConverter.FfiArg,
StringConverter.KeepAlive?,
) = StringConverter.convertArg(my_string_field)
let ffiStructArg = FfiArg(my_numeric_field: my_numeric_field_ffi, my_string_field: my_string_field_ffi, )
let ffiStructKeepAlive: (IdentityConverter<Int32>.KeepAlive?, StringConverter.KeepAlive?, )? =
(my_numeric_field_keepalive != nil || my_string_field_keepalive != nil || false)
? (my_numeric_field_keepalive, my_string_field_keepalive,) : nil
return (ffiStructArg, ffiStructKeepAlive)
}
static func convertArgBorrowed<Result>(
_ niceArg: NiceArg,
_ niceThunk: (FfiArg) throws -> Result,
) rethrows -> Result {
let my_numeric_field = niceArg.myNumericField
let my_string_field = niceArg.myStringField
return try IdentityConverter<Int32>.convertArgBorrowed(my_numeric_field) {
ffi_my_numeric_field in
return try StringConverter.convertArgBorrowed(my_string_field) {
ffi_my_string_field in
return try niceThunk(
FfiArg(
my_numeric_field: ffi_my_numeric_field,
my_string_field: ffi_my_string_field,
)
)
}
}
}
}
internal enum NativeTestingNice {
internal static func TESTING_MyTestEnum_identity(
x: MyTestEnum,
) throws -> MyTestEnum {
try DerivedArgConverterMyTestEnum.convertArgBorrowed(x) { xFfi in
var rawOutput = DerivedReturnConverterMyTestEnum.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_enum_identity(
&rawOutput,
xFfi,
)
)
return try DerivedReturnConverterMyTestEnum.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_MyTestEnum_to_string(
x: MyTestEnum,
) throws -> String {
try DerivedArgConverterMyTestEnum.convertArgBorrowed(x) { xFfi in
var rawOutput = StringConverter.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_enum_to_string(
&rawOutput,
xFfi,
)
)
return try StringConverter.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_MyTestPoint_identity(
x: MyTestPoint,
) throws -> MyTestPoint {
try DerivedArgConverterMyTestPoint.convertArgBorrowed(x) { xFfi in
var rawOutput = DerivedReturnConverterMyTestPoint.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_point_identity(
&rawOutput,
xFfi,
)
)
return try DerivedReturnConverterMyTestPoint.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_MyTestPoint_to_string(
x: MyTestPoint,
) throws -> String {
try DerivedArgConverterMyTestPoint.convertArgBorrowed(x) { xFfi in
var rawOutput = StringConverter.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_point_to_string(
&rawOutput,
xFfi,
)
)
return try StringConverter.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_MyTestStruct_identity(
x: MyTestStruct,
) throws -> MyTestStruct {
try DerivedArgConverterMyTestStruct.convertArgBorrowed(x) { xFfi in
var rawOutput = DerivedReturnConverterMyTestStruct.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_struct_identity(
&rawOutput,
xFfi,
)
)
return try DerivedReturnConverterMyTestStruct.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_MyTestStruct_to_string(
x: MyTestStruct,
) throws -> String {
try DerivedArgConverterMyTestStruct.convertArgBorrowed(x) { xFfi in
var rawOutput = StringConverter.emptyFfiReturn()
try checkError(
SignalFfi.signal_testing_my_test_struct_to_string(
&rawOutput,
xFfi,
)
)
return try StringConverter.convertReturn(consuming: rawOutput)
}
}
internal static func TESTING_TestingIntBox_Get(
myIntBox my_int_box: TestingIntBox,
) throws -> Int32 {

View File

@ -11,29 +11,58 @@ import Foundation
import SignalFfi
import Testing
struct NativeTestingNiceTests {
private func testConversion<Item: Equatable>(
items: any Sequence<Item>,
toString: (Item) throws -> String,
nativeToString: (Item) throws -> String,
nativeIdentity: (Item) throws -> Item,
extension NiceArgConverter {
fileprivate static func testConversion(
items: any Sequence<NiceArg>,
toString: (NiceArg) throws -> String,
nativeToString: (NiceArg) throws -> String,
rawNativeToString: (UnsafeMutablePointer<UnsafePointer<CChar>?>?, FfiArg) -> SignalFfiErrorRef?,
nativeIdentity: (NiceArg) throws -> NiceArg,
) throws {
for item in items {
let swiftString = try toString(item)
let nativeString = try nativeToString(item)
#expect(swiftString == nativeString)
let actualIdentity = try nativeIdentity(item)
#expect(item == actualIdentity)
let actualIdentityString = try toString(actualIdentity)
#expect(actualIdentityString == nativeString)
// Manually check both the borrowed and keep alive forms
let rawBorrowedNativeString = try self.convertArgBorrowed(item) { itemFfi in
var rawOutput = StringConverter.emptyFfiReturn()
try checkError(
rawNativeToString(
&rawOutput,
itemFfi,
)
)
return try StringConverter.convertReturn(consuming: rawOutput)
}
#expect(swiftString == rawBorrowedNativeString)
let rawKeepAliveNativeString = try self.genericArgBorrowed(item) { itemFfi in
var rawOutput = StringConverter.emptyFfiReturn()
try checkError(
rawNativeToString(
&rawOutput,
itemFfi,
)
)
return try StringConverter.convertReturn(consuming: rawOutput)
}
#expect(swiftString == rawKeepAliveNativeString)
}
}
}
struct NativeTestingNiceTests {
@Test
func testString() throws {
try testConversion(
try StringConverter.testConversion(
items: ["", "abc", "îüéè"],
toString: { $0 },
nativeToString: {
try NativeTestingNice.TESTING_conversion_string_identity(x: $0)
},
rawNativeToString: SignalFfi.signal_testing_conversion_string_identity,
nativeIdentity: {
try NativeTestingNice.TESTING_conversion_string_identity(x: $0)
},
@ -41,43 +70,47 @@ struct NativeTestingNiceTests {
}
@Test
func testBool() throws {
try testConversion(
try IdentityConverter<Bool>.testConversion(
items: [true, false],
toString: { "\($0)" },
nativeToString: { try NativeTestingNice.TESTING_conversion_bool_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_bool_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_bool_identity(x: $0) }
)
}
@Test
func testU8() throws {
try testConversion(
try IdentityConverter<UInt8>.testConversion(
items: UInt8.min...UInt8.max,
toString: { "\($0)" },
nativeToString: { try NativeTestingNice.TESTING_conversion_u8_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_u8_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_u8_identity(x: $0) }
)
}
@Test
func testU16() throws {
try testConversion(
try IdentityConverter<UInt16>.testConversion(
items: UInt16.min...UInt16.max,
toString: { "\($0)" },
nativeToString: { try NativeTestingNice.TESTING_conversion_u16_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_u16_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_u16_identity(x: $0) }
)
}
@Test
func testI32() throws {
try testConversion(
try IdentityConverter<Int32>.testConversion(
items: -1024...1024,
toString: { "\($0)" },
nativeToString: { try NativeTestingNice.TESTING_conversion_i32_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_i32_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_i32_identity(x: $0) }
)
}
@Test
func testServiceId() throws {
try testConversion(
try ServiceIdConverter.testConversion(
items: [
Aci(fromUUID: UUID()),
Pni(fromUUID: UUID()),
@ -86,19 +119,74 @@ struct NativeTestingNiceTests {
],
toString: { $0.serviceIdString },
nativeToString: { try NativeTestingNice.TESTING_conversion_ServiceId_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_service_id_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_ServiceId_identity(x: $0) }
)
}
@Test
func testData() throws {
try testConversion(
try DataConverter.testConversion(
items: (0..<10).lazy.map { count in Data((0..<(1 << count)).map { _ in UInt8.random(in: 0...255) }) },
toString: { $0.base64EncodedString() },
nativeToString: { try NativeTestingNice.TESTING_conversion_Data_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_conversion_data_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_conversion_Data_identity(x: $0) }
)
}
@Test
func testMyTestPoint() throws {
try DerivedArgConverterMyTestPoint.testConversion(
items: [MyTestPoint(1, 2)],
toString: { "[\($0._0),\($0._1)]" },
nativeToString: { try NativeTestingNice.TESTING_MyTestPoint_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_my_test_point_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_MyTestPoint_identity(x: $0) },
)
}
@Test
func testMyTestStruct() throws {
try DerivedArgConverterMyTestStruct.testConversion(
items: [MyTestStruct(myNumericField: 123, myStringField: "string!")],
toString: { "{\"myNumericField\":\($0.myNumericField),\"myStringField\":\"\($0.myStringField)\"}" },
nativeToString: { try NativeTestingNice.TESTING_MyTestStruct_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_my_test_struct_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_MyTestStruct_identity(x: $0) },
)
}
@Test
func testMyTestEnum() throws {
try DerivedArgConverterMyTestEnum.testConversion(
items: [
.unit,
.single(73),
.record(
personName: "Person!",
personAge: 101,
position: MyTestPoint(3, 4),
funStruct: MyTestStruct(myNumericField: 847, myStringField: "string!")
),
.singleNamed(x: 847),
.double(8, 9),
],
toString: { value in
return switch value {
case .double(let x, let y): #"{"double":[\#(x),\#(y)]}"#
case .record(let personName, let personAge, let position, let funStruct):
#"{"record":{"personName":"\#(personName)","personAge":\#(personAge),"#
+ #""position":[\#(position._0),\#(position._1)],"#
+ #""funStruct":{"myNumericField":\#(funStruct.myNumericField),"#
+ #""myStringField":"\#(funStruct.myStringField)"}}}"#
case .single(let x): #"{"single":\#(x)}"#
case .singleNamed(let x): #"{"singleNamed":{"x":\#(x)}}"#
case .unit: #""unit""#
}
},
nativeToString: { try NativeTestingNice.TESTING_MyTestEnum_to_string(x: $0) },
rawNativeToString: SignalFfi.signal_testing_my_test_enum_to_string,
nativeIdentity: { try NativeTestingNice.TESTING_MyTestEnum_identity(x: $0) },
)
}
@Test
func asyncTest() async throws {
let ctx = TokioAsyncContext()
for c in [0, 1, 2, 4, 8, 16, 32, 64, 128, 256] {

View File

@ -3,10 +3,13 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalFfi
import XCTest
@testable import LibSignalClient
let SECONDS_PER_DAY: UInt64 = 24 * 60 * 60
class BadStore: InMemorySignalProtocolStore {
enum Error: Swift.Error {
case badness
@ -61,6 +64,24 @@ extension RangeReplaceableCollection where Element == UInt8 {
}
}
extension Randomness {
internal init?(fromHexString hex: String) {
guard let array = [UInt8](fromHexString: hex) else {
return nil
}
var bytes: SignalRandomnessBytes = (
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
)
if array.count != MemoryLayout.size(ofValue: bytes) {
return nil
}
withUnsafeMutableBytes(of: &bytes) {
$0.copyBytes(from: array)
}
self.init(bytes)
}
}
// Helper for async error assertions until XCTest supports async autoclosures
// Adapted from https://arturgruchala.com/testing-async-await-exceptions/
func assertThrowsErrorAsync<T>(

View File

@ -7,8 +7,6 @@ import Foundation
import LibSignalClient
import Testing
private let SECONDS_PER_DAY: UInt64 = 24 * 60 * 60
class ZKGroupTests {
let TEST_ARRAY_16: UUID = .init(
uuid: (0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F)

View File

@ -1,13 +1,13 @@
{
"name": "LibSignalClient",
"version": "0.94.4",
"version": "0.95.0",
"summary": "A Swift wrapper library for communicating with the Signal messaging service.",
"homepage": "https://github.com/signalapp/libsignal",
"license": "AGPL-3.0-only",
"authors": "Signal Messenger LLC",
"source": {
"git": "https://github.com/signalapp/libsignal.git",
"tag": "v0.94.4"
"tag": "v0.95.0"
},
"swift_versions": "5",
"platforms": {
@ -30,8 +30,8 @@
"LIBSIGNAL_FFI_TEMP_DIR": "$(PROJECT_TEMP_DIR)/libsignal_ffi",
"LIBSIGNAL_FFI_LIB_TO_LINK": "$(LIBSIGNAL_FFI_TEMP_DIR)/$(LIBSIGNAL_FFI_BUILD_PATH)/libsignal_ffi.a",
"OTHER_LDFLAGS": "$(LIBSIGNAL_FFI_LIB_TO_LINK)",
"LIBSIGNAL_FFI_PREBUILD_ARCHIVE": "libsignal-client-ios-build-v0.94.4.tar.gz",
"LIBSIGNAL_FFI_PREBUILD_CHECKSUM": "273236d44fdd2eb76f18de0d4229dd82d73ac1edb2e52e71885c6f98843a9c0d",
"LIBSIGNAL_FFI_PREBUILD_ARCHIVE": "libsignal-client-ios-build-v0.95.0.tar.gz",
"LIBSIGNAL_FFI_PREBUILD_CHECKSUM": "79f53932ff82f792b70e30bad3b38801da0b882137adaf65ad54d907a94f3d29",
"CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=arm64]": "aarch64-apple-ios-sim",
"CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=*]": "x86_64-apple-ios",
"CARGO_BUILD_TARGET[sdk=iphoneos*][arch=arm64e]": "arm64e-apple-ios",

View File

@ -9,8 +9,8 @@ PODS:
- LibMobileCoin/CoreHTTP (6.0.2):
- SwiftProtobuf (~> 1.5)
- libPhoneNumber-iOS (1.2.0)
- LibSignalClient (0.94.4)
- LibSignalClient/Tests (0.94.4)
- LibSignalClient (0.95.0)
- LibSignalClient/Tests (0.95.0)
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
@ -52,8 +52,8 @@ DEPENDENCIES:
- GRDB.swift/SQLCipher
- LibMobileCoin/CoreHTTP (from `https://github.com/signalapp/libmobilecoin-ios-artifacts`, tag `signal/6.0.2`)
- libPhoneNumber-iOS (from `https://github.com/signalapp/libPhoneNumber-iOS`, branch `signal-master`)
- LibSignalClient (from `https://github.com/signalapp/libsignal.git`, tag `v0.94.4`)
- LibSignalClient/Tests (from `https://github.com/signalapp/libsignal.git`, tag `v0.94.4`)
- LibSignalClient (from `https://github.com/signalapp/libsignal.git`, tag `v0.95.0`)
- LibSignalClient/Tests (from `https://github.com/signalapp/libsignal.git`, tag `v0.95.0`)
- libwebp
- lottie-ios
- MobileCoin/CoreHTTP (from `https://github.com/mobilecoinofficial/MobileCoin-Swift`, tag `v6.0.3`)
@ -89,7 +89,7 @@ EXTERNAL SOURCES:
:git: https://github.com/signalapp/libPhoneNumber-iOS
LibSignalClient:
:git: https://github.com/signalapp/libsignal.git
:tag: v0.94.4
:tag: v0.95.0
MobileCoin:
:git: https://github.com/mobilecoinofficial/MobileCoin-Swift
:tag: v6.0.3
@ -113,7 +113,7 @@ CHECKOUT OPTIONS:
:git: https://github.com/signalapp/libPhoneNumber-iOS
LibSignalClient:
:git: https://github.com/signalapp/libsignal.git
:tag: v0.94.4
:tag: v0.95.0
MobileCoin:
:git: https://github.com/mobilecoinofficial/MobileCoin-Swift
:tag: v6.0.3
@ -131,7 +131,7 @@ SPEC CHECKSUMS:
GRDB.swift: 1395cb3556df6b16ed69dfc74c3886abc75d2825
LibMobileCoin: 8503f567fa32184a5be7bc038fbd727747dd9991
libPhoneNumber-iOS: 1a34106b49dc6e12a7f37eb9aee7c64011509547
LibSignalClient: 08d66ae1b4e9c93f9e6930cc0260df27c14ecd03
LibSignalClient: a98db1d538243e43ecac040005204bd274cbd8c7
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Logging: beeb016c9c80cf77042d62e83495816847ef108b
lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418
@ -143,6 +143,6 @@ SPEC CHECKSUMS:
SQLCipher: ff2f045b20d675a73a70f7329395ddd4a2580063
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
PODFILE CHECKSUM: e3d9c2375b2f8bb1af334adb83effea5eedff4fb
PODFILE CHECKSUM: ee98007764e1569e9dbe4f25053510725b19fc88
COCOAPODS: 1.15.2

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.94.4</string>
<string>0.95.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -18,8 +18,8 @@ HEADER_SEARCH_PATHS = $(inherited) $(PODS_TARGET_SRCROOT)/swift/Sources/SignalFf
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
LIBSIGNAL_FFI_BUILD_PATH = target/$(CARGO_BUILD_TARGET)/release
LIBSIGNAL_FFI_LIB_TO_LINK = $(LIBSIGNAL_FFI_TEMP_DIR)/$(LIBSIGNAL_FFI_BUILD_PATH)/libsignal_ffi.a
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.94.4.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 273236d44fdd2eb76f18de0d4229dd82d73ac1edb2e52e71885c6f98843a9c0d
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.95.0.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 79f53932ff82f792b70e30bad3b38801da0b882137adaf65ad54d907a94f3d29
LIBSIGNAL_FFI_TEMP_DIR = $(PROJECT_TEMP_DIR)/libsignal_ffi
OTHER_LDFLAGS = $(LIBSIGNAL_FFI_LIB_TO_LINK) $(inherited)
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS

View File

@ -18,8 +18,8 @@ HEADER_SEARCH_PATHS = $(inherited) $(PODS_TARGET_SRCROOT)/swift/Sources/SignalFf
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
LIBSIGNAL_FFI_BUILD_PATH = target/$(CARGO_BUILD_TARGET)/release
LIBSIGNAL_FFI_LIB_TO_LINK = $(LIBSIGNAL_FFI_TEMP_DIR)/$(LIBSIGNAL_FFI_BUILD_PATH)/libsignal_ffi.a
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.94.4.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 273236d44fdd2eb76f18de0d4229dd82d73ac1edb2e52e71885c6f98843a9c0d
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.95.0.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 79f53932ff82f792b70e30bad3b38801da0b882137adaf65ad54d907a94f3d29
LIBSIGNAL_FFI_TEMP_DIR = $(PROJECT_TEMP_DIR)/libsignal_ffi
OTHER_LDFLAGS = $(LIBSIGNAL_FFI_LIB_TO_LINK) $(inherited)
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS

View File

@ -19,8 +19,8 @@ LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer
LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
LIBSIGNAL_FFI_BUILD_PATH = target/$(CARGO_BUILD_TARGET)/release
LIBSIGNAL_FFI_LIB_TO_LINK =
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.94.4.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 273236d44fdd2eb76f18de0d4229dd82d73ac1edb2e52e71885c6f98843a9c0d
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.95.0.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 79f53932ff82f792b70e30bad3b38801da0b882137adaf65ad54d907a94f3d29
LIBSIGNAL_FFI_TEMP_DIR = $(PROJECT_TEMP_DIR)/libsignal_ffi
OTHER_LDFLAGS = $(LIBSIGNAL_FFI_LIB_TO_LINK) $(inherited) -ObjC -framework "LibSignalClient"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS

View File

@ -19,8 +19,8 @@ LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer
LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
LIBSIGNAL_FFI_BUILD_PATH = target/$(CARGO_BUILD_TARGET)/release
LIBSIGNAL_FFI_LIB_TO_LINK =
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.94.4.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 273236d44fdd2eb76f18de0d4229dd82d73ac1edb2e52e71885c6f98843a9c0d
LIBSIGNAL_FFI_PREBUILD_ARCHIVE = libsignal-client-ios-build-v0.95.0.tar.gz
LIBSIGNAL_FFI_PREBUILD_CHECKSUM = 79f53932ff82f792b70e30bad3b38801da0b882137adaf65ad54d907a94f3d29
LIBSIGNAL_FFI_TEMP_DIR = $(PROJECT_TEMP_DIR)/libsignal_ffi
OTHER_LDFLAGS = $(LIBSIGNAL_FFI_LIB_TO_LINK) $(inherited) -ObjC -framework "LibSignalClient"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS