Skip to content

Commit fc6ea3f

Browse files
committed
BLIP 42 support
1 parent 4aa5727 commit fc6ea3f

20 files changed

Lines changed: 405 additions & 109 deletions

File tree

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Versions {
2-
const val lightningKmp = "1.8.4"
2+
const val lightningKmp = "1.8.5-SNAPSHOT"
33
const val secp256k1 = "0.14.0"
44

55
const val kotlin = "1.9.22"

phoenix-ios/phoenix-ios/Localizable.xcstrings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34795,6 +34795,9 @@
3479534795
}
3479634796
}
3479734797
}
34798+
},
34799+
"Secrets: (DEBUG build only)" : {
34800+
3479834801
},
3479934802
"Security" : {
3480034803
"extractionState" : "manual",

phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ extension WalletPaymentInfo {
189189
var msg: String? = nil
190190

191191
if let incomingOfferMetadata = payment.incomingOfferMetadata() {
192-
msg = incomingOfferMetadata.payerNote
192+
msg = incomingOfferMetadata.payerNote_
193193

194194
} else if let outgoingInvoiceRequest = payment.outgoingInvoiceRequest() {
195195
msg = outgoingInvoiceRequest.payerNote
@@ -209,7 +209,24 @@ extension WalletPaymentInfo {
209209

210210
func addToContactsInfo() -> AddToContactsInfo? {
211211

212-
if payment is Lightning_kmpOutgoingPayment {
212+
if let incoming = payment as? Lightning_kmpIncomingPayment {
213+
214+
if let metadata = payment.incomingOfferMetadataV2() {
215+
if let rawSecret = metadata.contactSecret {
216+
let offer = metadata.payerOffer
217+
let address = metadata.payerAddress?.description()
218+
if (offer != nil) || (address != nil) {
219+
let secret = ContactSecret(
220+
id: rawSecret,
221+
incomingPaymentId: incoming.paymentHash,
222+
createdAt: Date.now.toInstant()
223+
)
224+
return AddToContactsInfo(offer: offer, address: address, secret: secret)
225+
}
226+
}
227+
}
228+
229+
} else if payment is Lightning_kmpOutgoingPayment {
213230

214231
// First check for a lightning address.
215232
// Remember that an outgoing payment might have both an address & offer (i.e. BIP-353).
@@ -221,12 +238,11 @@ extension WalletPaymentInfo {
221238
// But that's a different feature. The user's perspective remains the same.
222239
//
223240
if let address = self.metadata.lightningAddress {
224-
return AddToContactsInfo(offer: nil, address: address)
241+
return AddToContactsInfo(offer: nil, address: address, secret: nil)
225242
}
226243

227-
let invoiceRequest = payment.outgoingInvoiceRequest()
228-
if let offer = invoiceRequest?.offer {
229-
return AddToContactsInfo(offer: offer, address: nil)
244+
if let offer = payment.outgoingInvoiceRequest()?.offer {
245+
return AddToContactsInfo(offer: offer, address: nil, secret: nil)
230246
}
231247
}
232248

phoenix-ios/phoenix-ios/views/contacts/AddToContactsInfo.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import PhoenixShared
44
struct AddToContactsInfo: Hashable {
55
let offer: Lightning_kmpOfferTypesOffer?
66
let address: String?
7+
let secret: ContactSecret?
78
}

phoenix-ios/phoenix-ios/views/contacts/ManageContact.swift

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ struct ManageContact: View {
9898
@State private var editAddress_text: String = ""
9999
@State private var editAddress_invalidReason: InvalidReason? = nil
100100

101+
@State private var secrets: [ContactSecret]
102+
@State private var secrets_hasChanges: Bool
103+
101104
enum FooterType: Int {
102105
case expanded_standard = 1
103106
case expanded_squeezed = 2
@@ -180,7 +183,6 @@ struct ManageContact: View {
180183
hasNewOffer = true
181184
}
182185
}
183-
184186

185187
self._offers = State(initialValue: rows)
186188
self._offers_hasChanges = State(initialValue: (contact != nil && hasNewOffer))
@@ -206,11 +208,34 @@ struct ManageContact: View {
206208
hasNewAddress = true
207209
}
208210
}
209-
210211

211212
self._addresses = State(initialValue: rows)
212213
self._addresses_hasChanges = State(initialValue: (contact != nil && hasNewAddress))
213214
}
215+
do {
216+
var set = Set<Bitcoin_kmpByteVector32>()
217+
var secrets = Array<ContactSecret>()
218+
219+
if let contact {
220+
for secret in contact.secrets {
221+
if !set.contains(secret.id) {
222+
set.insert(secret.id)
223+
secrets.append(secret)
224+
}
225+
}
226+
}
227+
var hasNewSecret = false
228+
if let newSecret = info?.secret {
229+
if !set.contains(newSecret.id) {
230+
set.insert(newSecret.id)
231+
secrets.append(newSecret)
232+
hasNewSecret = true
233+
}
234+
}
235+
236+
self._secrets = State(initialValue: secrets)
237+
self._secrets_hasChanges = State(initialValue: (contact != nil && hasNewSecret))
238+
}
214239
}
215240

216241
// --------------------------------------------------
@@ -389,6 +414,9 @@ struct ManageContact: View {
389414
content_trusted()
390415
content_offers()
391416
content_addresses()
417+
#if DEBUG
418+
content_secrets()
419+
#endif
392420
} // </VStack>
393421
.padding()
394422
} // </ScrollView>
@@ -927,6 +955,67 @@ struct ManageContact: View {
927955
.padding(.top, ROW_VERTICAL_SPACING)
928956
}
929957

958+
@ViewBuilder
959+
func content_secrets() -> some View {
960+
961+
VStack(alignment: HorizontalAlignment.leading, spacing: 0) {
962+
963+
HStack(alignment: VerticalAlignment.center, spacing: 0) {
964+
Text("Secrets: (DEBUG build only)")
965+
Spacer(minLength: 0)
966+
} // </HStack>
967+
968+
VStack(alignment: HorizontalAlignment.leading, spacing: 0) {
969+
ForEach(0 ..< secrets.count, id: \.self) { idx in
970+
content_secret_row(idx)
971+
} // </ForEach>
972+
} // </VStack>
973+
974+
if secrets.isEmpty {
975+
content_secret_emptyRow()
976+
}
977+
978+
} // </VStack>
979+
.padding(.bottom, 30)
980+
}
981+
982+
@ViewBuilder
983+
func content_secret_row(_ index: Int) -> some View {
984+
985+
let row: ContactSecret = secrets[index]
986+
987+
HStack(alignment: VerticalAlignment.firstTextBaseline, spacing: 0) {
988+
989+
bullet()
990+
991+
VStack(alignment: HorizontalAlignment.leading, spacing: 4) {
992+
Text(row.id.toHex())
993+
.foregroundStyle(Color.primary)
994+
Text(verbatim: "incomingPaymentId: \( row.incomingPaymentId?.toHex() ?? "<nil>" )")
995+
.foregroundStyle(Color.secondary)
996+
}
997+
.lineLimit(1)
998+
.truncationMode(.middle)
999+
.font(.callout)
1000+
1001+
}
1002+
.padding(.top, ROW_VERTICAL_SPACING)
1003+
}
1004+
1005+
@ViewBuilder
1006+
func content_secret_emptyRow() -> some View {
1007+
1008+
HStack(alignment: VerticalAlignment.firstTextBaseline, spacing: 0) {
1009+
bullet()
1010+
Text("none")
1011+
.lineLimit(1)
1012+
.foregroundStyle(Color.secondary)
1013+
.layoutPriority(-1)
1014+
.font(.callout)
1015+
}
1016+
.padding(.top, ROW_VERTICAL_SPACING)
1017+
}
1018+
9301019
@ViewBuilder
9311020
func bullet() -> some View {
9321021

@@ -1283,7 +1372,7 @@ struct ManageContact: View {
12831372
if doNotUseDiskImage {
12841373
return true
12851374
}
1286-
if offers_hasChanges || addresses_hasChanges {
1375+
if offers_hasChanges || addresses_hasChanges || secrets_hasChanges {
12871376
return true
12881377
}
12891378

@@ -1480,9 +1569,12 @@ struct ManageContact: View {
14801569
photoUri: newPhotoName,
14811570
useOfferKey: updatedUseOfferKey,
14821571
offers: offers.map { $0.raw },
1483-
addresses: addresses.map { $0.raw }
1572+
addresses: addresses.map { $0.raw },
1573+
secrets: secrets
14841574
)
14851575

1576+
log.debug("updatedContact.secrets.count: \(updatedContact.secrets.count)")
1577+
14861578
try await Biz.business.contactsManager.saveContact(contact: updatedContact)
14871579

14881580
if let oldPhotoName, oldPhotoName != newPhotoName {

phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,8 +683,11 @@ struct LightningDualView: View {
683683
func generateQrCode() async {
684684

685685
do {
686-
let offerData = try await Biz.business.nodeParamsManager.defaultOffer()
687-
let offerString = offerData.defaultOffer.encode()
686+
let offerAndKey: Lightning_kmpOfferTypesOfferAndKey =
687+
try await Biz.business.nodeParamsManager.defaultOffer()
688+
689+
let offer: Lightning_kmpOfferTypesOffer = offerAndKey.offer
690+
let offerString: String = offer.encode()
688691

689692
offerStr = offerString
690693
if activeType == .bolt12_offer {

phoenix-ios/phoenix-ios/views/send/ValidateView.swift

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,7 @@ struct ValidateView: View {
16351635
return
16361636
}
16371637

1638-
let info = AddToContactsInfo(offer: offer, address: address)
1638+
let info = AddToContactsInfo(offer: offer, address: address, secret: nil)
16391639

16401640
let count: Int = Biz.business.contactsManager.contactsListCurrentValue().count
16411641
if count == 0 {
@@ -1821,11 +1821,53 @@ struct ValidateView: View {
18211821
Biz.beginLongLivedTask(id: paymentId.description())
18221822

18231823
let payerKey: Bitcoin_kmpPrivateKey
1824-
if contact?.useOfferKey ?? false {
1825-
let offerData = try await Biz.business.nodeParamsManager.defaultOffer()
1826-
payerKey = offerData.payerKey
1824+
let contactSecret: Bitcoin_kmpByteVector32?
1825+
1826+
if let contact, contact.useOfferKey {
1827+
let offerAndKey: Lightning_kmpOfferTypesOfferAndKey =
1828+
try await Biz.business.nodeParamsManager.defaultOffer()
1829+
1830+
payerKey = offerAndKey.privateKey
1831+
1832+
if let existingSecret = contact.secrets.first {
1833+
// We already have a known secret with this contact.
1834+
// This could be because:
1835+
// A) we added the contact from an incoming payment which contained a secet
1836+
// B) we've already sent them a payment, and generated the secret in the past
1837+
contactSecret = existingSecret.id
1838+
1839+
} else {
1840+
// Generate a new secret using the recommended derivation algorithm.
1841+
let rawSecret: Lightning_kmpContactSecrets =
1842+
LightningExposureKt.Contacts_computeContactSecret(
1843+
ourOffer: offerAndKey,
1844+
theirOffer: model.offer
1845+
)
1846+
1847+
// Store the new secret to the database
1848+
let newSecret = ContactSecret(
1849+
id: rawSecret.primarySecret,
1850+
incomingPaymentId: nil,
1851+
createdAt: Date.now.toInstant()
1852+
)
1853+
let updatedContact = contact.doCopy(
1854+
id : contact.id,
1855+
name : contact.name,
1856+
photoUri : contact.photoUri,
1857+
useOfferKey : contact.useOfferKey,
1858+
offers : contact.offers,
1859+
addresses : contact.addresses,
1860+
secrets : contact.secrets + [newSecret]
1861+
)
1862+
try await Biz.business.contactsManager.saveContact(contact: updatedContact)
1863+
1864+
// Use the newly generated secret for this payment
1865+
contactSecret = newSecret.id
1866+
}
1867+
18271868
} else {
18281869
payerKey = Lightning_randomKey()
1870+
contactSecret = nil
18291871
}
18301872

18311873
let response: Lightning_kmpOfferNotPaid? =
@@ -1836,6 +1878,7 @@ struct ValidateView: View {
18361878
lightningAddress: model.lightningAddress,
18371879
payerKey: payerKey,
18381880
payerNote: payerNote,
1881+
contactSecret: contactSecret,
18391882
fetchInvoiceTimeoutInSeconds: 30
18401883
)
18411884

phoenix-shared/src/commonMain/appdb/fr.acinq.phoenix.db/Contacts.sq

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,19 @@ CREATE TABLE IF NOT EXISTS contact_addresses (
2929
FOREIGN KEY(contact_id) REFERENCES contacts(id)
3030
);
3131

32+
CREATE TABLE IF NOT EXISTS contact_secrets (
33+
secret_id BLOB NOT NULL PRIMARY KEY,
34+
contact_id TEXT NOT NULL,
35+
incoming_payment_id BLOB,
36+
created_at INTEGER NOT NULL,
37+
38+
FOREIGN KEY(contact_id) REFERENCES contacts(id)
39+
);
40+
3241
CREATE INDEX contact_name_index ON contacts(name ASC);
3342
CREATE INDEX contact_id_index ON contact_offers(contact_id);
3443
CREATE INDEX contact_id_index2 ON contact_addresses(contact_id);
44+
CREATE INDEX contact_id_index3 ON contact_secrets(contact_id);
3545

3646
-- ########## table: contacts ##########
3747

@@ -121,3 +131,29 @@ DELETE FROM contact_addresses WHERE address_hash=:addressHash;
121131

122132
deleteContactAddressesForContactId:
123133
DELETE FROM contact_addresses WHERE contact_id=:contactId;
134+
135+
-- ########## table: contact_secrets ##########
136+
137+
listContactSecrets:
138+
SELECT *
139+
FROM contact_secrets;
140+
141+
listSecretsForContact:
142+
SELECT *
143+
FROM contact_secrets
144+
WHERE contact_id=:contactId;
145+
146+
insertSecretForContact:
147+
INSERT INTO contact_secrets(secret_id, contact_id, incoming_payment_id, created_at)
148+
VALUES (:secretId, :contactId, :incomingPaymentId, :createdAt);
149+
150+
updateContactSecret:
151+
UPDATE contact_secrets
152+
SET incoming_payment_id=:incomingPaymentId
153+
WHERE secret_id=:secretId;
154+
155+
deleteContactSecretForSecretId:
156+
DELETE FROM contact_secrets WHERE secret_id=:secretId;
157+
158+
deleteContactSecretsForContactId:
159+
DELETE FROM contact_secrets WHERE contact_id=:contactId;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Migration: v8 -> v9
2+
--
3+
-- Changes:
4+
-- * Added table contact_secrets
5+
-- * Added index on table: contact_secrets
6+
--
7+
8+
CREATE TABLE IF NOT EXISTS contact_secrets (
9+
secret_id BLOB NOT NULL PRIMARY KEY,
10+
contact_id TEXT NOT NULL,
11+
incoming_payment_id BLOB,
12+
created_at INTEGER NOT NULL,
13+
14+
FOREIGN KEY(contact_id) REFERENCES contacts(id)
15+
);
16+
17+
CREATE INDEX contact_id_index3 ON contact_secrets(contact_id);

0 commit comments

Comments
 (0)