Skip to content

Commit

Permalink
BLIP 42 support
Browse files Browse the repository at this point in the history
  • Loading branch information
robbiehanson committed Oct 28, 2024
1 parent dbb8bcf commit e924811
Show file tree
Hide file tree
Showing 20 changed files with 405 additions and 109 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Versions {
const val lightningKmp = "1.8.4"
const val lightningKmp = "1.8.5-SNAPSHOT"
const val secp256k1 = "0.14.0"

const val kotlin = "1.9.22"
Expand Down
3 changes: 3 additions & 0 deletions phoenix-ios/phoenix-ios/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -34795,6 +34795,9 @@
}
}
}
},
"Secrets: (DEBUG build only)" : {

},
"Security" : {
"extractionState" : "manual",
Expand Down
28 changes: 22 additions & 6 deletions phoenix-ios/phoenix-ios/kotlin/KotlinExtensions+Payments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ extension WalletPaymentInfo {
var msg: String? = nil

if let incomingOfferMetadata = payment.incomingOfferMetadata() {
msg = incomingOfferMetadata.payerNote
msg = incomingOfferMetadata.payerNote_

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

func addToContactsInfo() -> AddToContactsInfo? {

if payment is Lightning_kmpOutgoingPayment {
if let incoming = payment as? Lightning_kmpIncomingPayment {

if let metadata = payment.incomingOfferMetadataV2() {
if let rawSecret = metadata.contactSecret {
let offer = metadata.payerOffer
let address = metadata.payerAddress?.description()
if (offer != nil) || (address != nil) {
let secret = ContactSecret(
id: rawSecret,
incomingPaymentId: incoming.paymentHash,
createdAt: Date.now.toInstant()
)
return AddToContactsInfo(offer: offer, address: address, secret: secret)
}
}
}

} else if payment is Lightning_kmpOutgoingPayment {

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

let invoiceRequest = payment.outgoingInvoiceRequest()
if let offer = invoiceRequest?.offer {
return AddToContactsInfo(offer: offer, address: nil)
if let offer = payment.outgoingInvoiceRequest()?.offer {
return AddToContactsInfo(offer: offer, address: nil, secret: nil)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import PhoenixShared
struct AddToContactsInfo: Hashable {
let offer: Lightning_kmpOfferTypesOffer?
let address: String?
let secret: ContactSecret?
}
100 changes: 96 additions & 4 deletions phoenix-ios/phoenix-ios/views/contacts/ManageContact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ struct ManageContact: View {
@State private var editAddress_text: String = ""
@State private var editAddress_invalidReason: InvalidReason? = nil

@State private var secrets: [ContactSecret]
@State private var secrets_hasChanges: Bool

enum FooterType: Int {
case expanded_standard = 1
case expanded_squeezed = 2
Expand Down Expand Up @@ -180,7 +183,6 @@ struct ManageContact: View {
hasNewOffer = true
}
}


self._offers = State(initialValue: rows)
self._offers_hasChanges = State(initialValue: (contact != nil && hasNewOffer))
Expand All @@ -206,11 +208,34 @@ struct ManageContact: View {
hasNewAddress = true
}
}


self._addresses = State(initialValue: rows)
self._addresses_hasChanges = State(initialValue: (contact != nil && hasNewAddress))
}
do {
var set = Set<Bitcoin_kmpByteVector32>()
var secrets = Array<ContactSecret>()

if let contact {
for secret in contact.secrets {
if !set.contains(secret.id) {
set.insert(secret.id)
secrets.append(secret)
}
}
}
var hasNewSecret = false
if let newSecret = info?.secret {
if !set.contains(newSecret.id) {
set.insert(newSecret.id)
secrets.append(newSecret)
hasNewSecret = true
}
}

self._secrets = State(initialValue: secrets)
self._secrets_hasChanges = State(initialValue: (contact != nil && hasNewSecret))
}
}

// --------------------------------------------------
Expand Down Expand Up @@ -389,6 +414,9 @@ struct ManageContact: View {
content_trusted()
content_offers()
content_addresses()
#if DEBUG
content_secrets()
#endif
} // </VStack>
.padding()
} // </ScrollView>
Expand Down Expand Up @@ -927,6 +955,67 @@ struct ManageContact: View {
.padding(.top, ROW_VERTICAL_SPACING)
}

@ViewBuilder
func content_secrets() -> some View {

VStack(alignment: HorizontalAlignment.leading, spacing: 0) {

HStack(alignment: VerticalAlignment.center, spacing: 0) {
Text("Secrets: (DEBUG build only)")
Spacer(minLength: 0)
} // </HStack>

VStack(alignment: HorizontalAlignment.leading, spacing: 0) {
ForEach(0 ..< secrets.count, id: \.self) { idx in
content_secret_row(idx)
} // </ForEach>
} // </VStack>

if secrets.isEmpty {
content_secret_emptyRow()
}

} // </VStack>
.padding(.bottom, 30)
}

@ViewBuilder
func content_secret_row(_ index: Int) -> some View {

let row: ContactSecret = secrets[index]

HStack(alignment: VerticalAlignment.firstTextBaseline, spacing: 0) {

bullet()

VStack(alignment: HorizontalAlignment.leading, spacing: 4) {
Text(row.id.toHex())
.foregroundStyle(Color.primary)
Text(verbatim: "incomingPaymentId: \( row.incomingPaymentId?.toHex() ?? "<nil>" )")
.foregroundStyle(Color.secondary)
}
.lineLimit(1)
.truncationMode(.middle)
.font(.callout)

}
.padding(.top, ROW_VERTICAL_SPACING)
}

@ViewBuilder
func content_secret_emptyRow() -> some View {

HStack(alignment: VerticalAlignment.firstTextBaseline, spacing: 0) {
bullet()
Text("none")
.lineLimit(1)
.foregroundStyle(Color.secondary)
.layoutPriority(-1)
.font(.callout)
}
.padding(.top, ROW_VERTICAL_SPACING)
}

@ViewBuilder
func bullet() -> some View {

Expand Down Expand Up @@ -1283,7 +1372,7 @@ struct ManageContact: View {
if doNotUseDiskImage {
return true
}
if offers_hasChanges || addresses_hasChanges {
if offers_hasChanges || addresses_hasChanges || secrets_hasChanges {
return true
}

Expand Down Expand Up @@ -1480,9 +1569,12 @@ struct ManageContact: View {
photoUri: newPhotoName,
useOfferKey: updatedUseOfferKey,
offers: offers.map { $0.raw },
addresses: addresses.map { $0.raw }
addresses: addresses.map { $0.raw },
secrets: secrets
)

log.debug("updatedContact.secrets.count: \(updatedContact.secrets.count)")

try await Biz.business.contactsManager.saveContact(contact: updatedContact)

if let oldPhotoName, oldPhotoName != newPhotoName {
Expand Down
7 changes: 5 additions & 2 deletions phoenix-ios/phoenix-ios/views/receive/LightningDualView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,11 @@ struct LightningDualView: View {
func generateQrCode() async {

do {
let offerData = try await Biz.business.nodeParamsManager.defaultOffer()
let offerString = offerData.defaultOffer.encode()
let offerAndKey: Lightning_kmpOfferTypesOfferAndKey =
try await Biz.business.nodeParamsManager.defaultOffer()

let offer: Lightning_kmpOfferTypesOffer = offerAndKey.offer
let offerString: String = offer.encode()

offerStr = offerString
if activeType == .bolt12_offer {
Expand Down
51 changes: 47 additions & 4 deletions phoenix-ios/phoenix-ios/views/send/ValidateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1635,7 +1635,7 @@ struct ValidateView: View {
return
}

let info = AddToContactsInfo(offer: offer, address: address)
let info = AddToContactsInfo(offer: offer, address: address, secret: nil)

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

let payerKey: Bitcoin_kmpPrivateKey
if contact?.useOfferKey ?? false {
let offerData = try await Biz.business.nodeParamsManager.defaultOffer()
payerKey = offerData.payerKey
let contactSecret: Bitcoin_kmpByteVector32?

if let contact, contact.useOfferKey {
let offerAndKey: Lightning_kmpOfferTypesOfferAndKey =
try await Biz.business.nodeParamsManager.defaultOffer()

payerKey = offerAndKey.privateKey

if let existingSecret = contact.secrets.first {
// We already have a known secret with this contact.
// This could be because:
// A) we added the contact from an incoming payment which contained a secet
// B) we've already sent them a payment, and generated the secret in the past
contactSecret = existingSecret.id

} else {
// Generate a new secret using the recommended derivation algorithm.
let rawSecret: Lightning_kmpContactSecrets =
LightningExposureKt.Contacts_computeContactSecret(
ourOffer: offerAndKey,
theirOffer: model.offer
)

// Store the new secret to the database
let newSecret = ContactSecret(
id: rawSecret.primarySecret,
incomingPaymentId: nil,
createdAt: Date.now.toInstant()
)
let updatedContact = contact.doCopy(
id : contact.id,
name : contact.name,
photoUri : contact.photoUri,
useOfferKey : contact.useOfferKey,
offers : contact.offers,
addresses : contact.addresses,
secrets : contact.secrets + [newSecret]
)
try await Biz.business.contactsManager.saveContact(contact: updatedContact)

// Use the newly generated secret for this payment
contactSecret = newSecret.id
}

} else {
payerKey = Lightning_randomKey()
contactSecret = nil
}

let response: Lightning_kmpOfferNotPaid? =
Expand All @@ -1836,6 +1878,7 @@ struct ValidateView: View {
lightningAddress: model.lightningAddress,
payerKey: payerKey,
payerNote: payerNote,
contactSecret: contactSecret,
fetchInvoiceTimeoutInSeconds: 30
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,19 @@ CREATE TABLE IF NOT EXISTS contact_addresses (
FOREIGN KEY(contact_id) REFERENCES contacts(id)
);

CREATE TABLE IF NOT EXISTS contact_secrets (
secret_id BLOB NOT NULL PRIMARY KEY,
contact_id TEXT NOT NULL,
incoming_payment_id BLOB,
created_at INTEGER NOT NULL,

FOREIGN KEY(contact_id) REFERENCES contacts(id)
);

CREATE INDEX contact_name_index ON contacts(name ASC);
CREATE INDEX contact_id_index ON contact_offers(contact_id);
CREATE INDEX contact_id_index2 ON contact_addresses(contact_id);
CREATE INDEX contact_id_index3 ON contact_secrets(contact_id);

-- ########## table: contacts ##########

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

deleteContactAddressesForContactId:
DELETE FROM contact_addresses WHERE contact_id=:contactId;

-- ########## table: contact_secrets ##########

listContactSecrets:
SELECT *
FROM contact_secrets;

listSecretsForContact:
SELECT *
FROM contact_secrets
WHERE contact_id=:contactId;

insertSecretForContact:
INSERT INTO contact_secrets(secret_id, contact_id, incoming_payment_id, created_at)
VALUES (:secretId, :contactId, :incomingPaymentId, :createdAt);

updateContactSecret:
UPDATE contact_secrets
SET incoming_payment_id=:incomingPaymentId
WHERE secret_id=:secretId;

deleteContactSecretForSecretId:
DELETE FROM contact_secrets WHERE secret_id=:secretId;

deleteContactSecretsForContactId:
DELETE FROM contact_secrets WHERE contact_id=:contactId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Migration: v8 -> v9
--
-- Changes:
-- * Added table contact_secrets
-- * Added index on table: contact_secrets
--

CREATE TABLE IF NOT EXISTS contact_secrets (
secret_id BLOB NOT NULL PRIMARY KEY,
contact_id TEXT NOT NULL,
incoming_payment_id BLOB,
created_at INTEGER NOT NULL,

FOREIGN KEY(contact_id) REFERENCES contacts(id)
);

CREATE INDEX contact_id_index3 ON contact_secrets(contact_id);
Loading

0 comments on commit e924811

Please sign in to comment.