Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Bitkit/Services/PubkyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1565,16 +1565,18 @@ final class PaykitReceiverNoiseKeyStore: @unchecked Sendable {
}

private func validatedKeyBytes() throws -> Data {
if let validatedBytes {
let storedBytes = try loadBytes()
if let validatedBytes, storedBytes == validatedBytes {
return validatedBytes
}
validatedBytes = nil

let derivedBytes = try deriveBytes()
guard derivedBytes.count == Self.keyLength else {
throw invalidKeyError("Derived Paykit receiver Noise key is invalid")
}

if let storedBytes = try loadBytes() {
if let storedBytes {
guard storedBytes.count == Self.keyLength else {
throw invalidKeyError("Stored Paykit receiver Noise key is invalid")
}
Expand Down
43 changes: 43 additions & 0 deletions BitkitTests/PaykitReceiverNoiseKeyStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,49 @@ final class PaykitReceiverNoiseKeyStoreTests: XCTestCase {
XCTAssertEqual(persistedBytes, Data(repeating: 1, count: 32))
}

func testReceiverNoiseKeyFollowsWalletReplacementAfterKeychainWipe() throws {
var persistedBytes: Data?
var derivedBytes = Data(repeating: 1, count: 32)
let store = PaykitReceiverNoiseKeyStore(
loadBytes: { persistedBytes },
upsertBytes: { persistedBytes = $0 },
deriveBytes: { derivedBytes }
)
let previousKey = try store.loadOrDerive()

persistedBytes = nil
derivedBytes = Data(repeating: 2, count: 32)

let replacementKey = try store.loadOrDerive().exportBytes()
XCTAssertEqual(replacementKey, derivedBytes)
XCTAssertEqual(persistedBytes, derivedBytes)
XCTAssertThrowsError(try store.persist(previousKey))

let restoredStore = PaykitReceiverNoiseKeyStore(
loadBytes: { persistedBytes },
upsertBytes: { persistedBytes = $0 },
deriveBytes: { derivedBytes }
)
XCTAssertEqual(try restoredStore.loadOrDerive().exportBytes(), replacementKey)
}

func testRejectsChangedPersistedKeyAfterCaching() throws {
let derivedBytes = Data(repeating: 1, count: 32)
var persistedBytes: Data? = derivedBytes
let store = PaykitReceiverNoiseKeyStore(
loadBytes: { persistedBytes },
upsertBytes: { _ in XCTFail("Invalid bytes must not be overwritten") },
deriveBytes: { derivedBytes }
)
_ = try store.loadOrDerive()

for invalidBytes in [Data(repeating: 1, count: 31), Data(repeating: 2, count: 32)] {
persistedBytes = invalidBytes
XCTAssertThrowsError(try store.loadOrDerive())
XCTAssertEqual(persistedBytes, invalidBytes)
}
}

func testRejectsInvalidPersistedReceiverNoiseKey() {
let store = PaykitReceiverNoiseKeyStore(
loadBytes: { Data(repeating: 0, count: 31) },
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/761.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed private payment connections reusing a previous wallet’s key after resetting the wallet.
Loading