From 7ababb4e1bb403e9475b9f8073e244796a7e4fee Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 28 Jan 2025 09:06:29 -0700 Subject: [PATCH] fix problem after minimizing use of account private data --- src/components/OnboardingDialog.vue | 2 +- src/views/ClaimCertificateView.vue | 44 ++++++++-- src/views/NewEditProjectView.vue | 123 +++++++++++++++++----------- 3 files changed, 111 insertions(+), 58 deletions(-) diff --git a/src/components/OnboardingDialog.vue b/src/components/OnboardingDialog.vue index abea4ec..053aa95 100644 --- a/src/components/OnboardingDialog.vue +++ b/src/components/OnboardingDialog.vue @@ -5,7 +5,7 @@

Welcome to Time Safari
- - Showcasing Gratitude & Magnifing Time + - Showcasing Gratitude & Magnifying Time
50 @@ -164,12 +192,12 @@ export default class ClaimCertificateView extends Vue { ); } - if ( - claimData.claim.object?.amountOfThisGood && - claimData.claim.object?.unitCode - ) { - const amount = claimData.claim.object.amountOfThisGood; - const unit = claimData.claim.object.unitCode; + const possibleObject = + claimData.claim.object || // for GiveActions + claimData.claim.includesObject; // for Offers + if (possibleObject?.amountOfThisGood && possibleObject?.unitCode) { + const amount = possibleObject.amountOfThisGood; + const unit = possibleObject.unitCode; const amountText = serverUtil.displayAmount(unit, amount); const amountWidth = ctx.measureText(amountText).width; // if there was no description then put this in that spot, otherwise put it below the description diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index 04d7d09..2f15508 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -203,8 +203,12 @@ import "leaflet/dist/leaflet.css"; import { AxiosError, AxiosRequestHeaders } from "axios"; import { DateTime } from "luxon"; import { hexToBytes } from "@noble/hashes/utils"; -import type { EventTemplate, VerifiedEvent } from "nostr-tools/lib/types/core"; -import { accountFromSeedWords } from "nostr-tools/nip06"; +// these core imports could also be included as "import type ..." +import { EventTemplate, UnsignedEvent, VerifiedEvent } from "nostr-tools/core"; +import { + accountFromExtendedKey, + extendedKeysFromSeedWords, +} from "nostr-tools/nip06"; import { finalizeEvent, serializeEvent } from "nostr-tools/pure"; import { Component, Vue } from "vue-facing-decorator"; import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet"; @@ -225,7 +229,6 @@ import { } from "@/libs/endorserServer"; import { retrieveAccountCount, - retrieveAccountMetadata, retrieveFullyDecryptedAccount, } from "@/libs/util"; @@ -472,31 +475,45 @@ export default class NewEditProjectView extends Vue { try { const resp = await this.axios.post(url, payload, { headers }); if (resp.data?.success?.handleId) { + this.$notify( + { + group: "alert", + type: "success", + title: "Saved", + text: "The project was saved successfully.", + }, + 3000, + ); + this.errorMessage = ""; const projectPath = encodeURIComponent(resp.data.success.handleId); if (this.sendToTrustroots || this.sendToTripHopping) { if (this.latitude && this.longitude) { - let signedPayload: VerifiedEvent | undefined; // sign something to prove ownership of pubkey + let payloadAndKey; // sign something to prove ownership of pubkey if (this.sendToTrustroots) { - signedPayload = await this.signPayload(); + payloadAndKey = await this.signSomePayload(); + // not going to await... the save was successful, so we'll continue to the next page this.sendToNostrPartner( "NOSTR-EVENT-TRUSTROOTS", "Trustroots", resp.data.success.claimId, - signedPayload, + payloadAndKey.signedEvent, + payloadAndKey.publicExtendedKey, ); } if (this.sendToTripHopping) { - if (!signedPayload) { - signedPayload = await this.signPayload(); + if (!payloadAndKey) { + payloadAndKey = await this.signSomePayload(); } + // not going to await... the save was successful, so we'll continue to the next page this.sendToNostrPartner( "NOSTR-EVENT-TRIPHOPPING", "TripHopping", resp.data.success.claimId, - signedPayload, + payloadAndKey.signedEvent, + payloadAndKey.publicExtendedKey, ); } } else { @@ -576,19 +593,28 @@ export default class NewEditProjectView extends Vue { } } - private async signPayload(): Promise { + /** + * @return a signed payload and an extended public key for later transmission + */ + private async signSomePayload(): Promise<{ + signedEvent: VerifiedEvent; + publicExtendedKey: string; + }> { const account = await retrieveFullyDecryptedAccount(this.activeDid); // get the last number of the derivationPath const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0]; // remove any trailing ' const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, ""); const accountNum = Number(finalDerNumNoApostrophe || 0); - const pubPri = accountFromSeedWords( + const extPubPri = extendedKeysFromSeedWords( account?.mnemonic as string, "", accountNum, ); - const privateBytes = hexToBytes(pubPri?.privateKey); + const publicExtendedKey: string = extPubPri?.publicExtendedKey; + const privateExtendedKey = extPubPri?.privateExtendedKey; + const privateKey = accountFromExtendedKey(privateExtendedKey).privateKey; + const privateBytes = hexToBytes(privateKey); // No real content is necessary, we just want something signed, // so we might as well use nostr libs for nostr functions. // Besides: someday we may create real content that we can relay. @@ -598,9 +624,12 @@ export default class NewEditProjectView extends Vue { content: "", created_at: 0, }; - // Why does IntelliJ not see matching types? - const signedEvent = finalizeEvent(event, privateBytes); - return signedEvent; + const signedEvent: VerifiedEvent = finalizeEvent( + // Why does IntelliJ not see matching types? + event as EventTemplate, + privateBytes, + ) as VerifiedEvent; + return { signedEvent, publicExtendedKey }; } private async sendToNostrPartner( @@ -608,41 +637,37 @@ export default class NewEditProjectView extends Vue { serviceName: string, jwtId: string, signedPayload: VerifiedEvent, + publicExtendedKey: string, ) { - // first, get the public key for nostr - const account = await retrieveAccountMetadata(this.activeDid); - // get the last number of the derivationPath - const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0]; - // remove any trailing ' - const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, ""); - const accountNum = Number(finalDerNumNoApostrophe || 0); - const pubPri = accountFromSeedWords( - account?.mnemonic as string, - "", - accountNum, - ); - const nostrPubKey = pubPri?.publicKey; - - let partnerServer = DEFAULT_PARTNER_API_SERVER; - const settings = await retrieveSettingsForActiveAccount(); - if (settings.partnerApiServer) { - partnerServer = settings.partnerApiServer; - } - const endorserPartnerUrl = partnerServer + "/api/partner/link"; - const timeSafariUrl = window.location.origin + "/claim/" + jwtId; - const content = this.fullClaim.name + " - see " + timeSafariUrl; - // Why does IntelliJ not see matching types? - const payload = serializeEvent(signedPayload); - const partnerParams = { - jwtId: jwtId, - linkCode: linkCode, - inputJson: JSON.stringify(content), - pubKeyHex: nostrPubKey, - pubKeyImage: payload, - pubKeySigHex: signedPayload.sig, - }; - const headers = await getHeaders(this.activeDid); try { + let partnerServer = DEFAULT_PARTNER_API_SERVER; + const settings = await retrieveSettingsForActiveAccount(); + if (settings.partnerApiServer) { + partnerServer = settings.partnerApiServer; + } + const endorserPartnerUrl = partnerServer + "/api/partner/link"; + const timeSafariUrl = window.location.origin + "/claim/" + jwtId; + const content = this.fullClaim.name + " - see " + timeSafariUrl; + const publicKeyHex = accountFromExtendedKey(publicExtendedKey).publicKey; + const unsignedPayload: UnsignedEvent = { + // why doesn't "...signedPayload" work? + kind: signedPayload.kind, + tags: signedPayload.tags, + content: signedPayload.content, + created_at: signedPayload.created_at, + pubkey: publicKeyHex, + }; + // Why does IntelliJ not see matching types? + const payload = serializeEvent(unsignedPayload as UnsignedEvent); + const partnerParams = { + jwtId: jwtId, + linkCode: linkCode, + inputJson: JSON.stringify(content), + pubKeyHex: publicKeyHex, + pubKeyImage: payload, + pubKeySigHex: signedPayload.sig, + }; + const headers = await getHeaders(this.activeDid); const linkResp = await this.axios.post( endorserPartnerUrl, partnerParams, @@ -731,7 +756,7 @@ export default class NewEditProjectView extends Vue { group: "alert", type: "info", title: "About Nostr Events", - text: "This will cause a submission to a partner on the nostr network. It will contain your public key data which may allow correlation, so don't allow this if you're not comfortable with that.", + text: "This will submit this project to a partner on the nostr network. It will contain your public key data which may allow correlation, so don't allow this if you're not comfortable with that.", }, 7000, );