release #111
@ -378,6 +378,54 @@ export class NostrController extends EventEmitter {
|
||||
throw new Error('Login method is undefined')
|
||||
}
|
||||
|
||||
nip04Decrypt = async (sender: string, content: string) => {
|
||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
||||
|
||||
if (loginMethod === LoginMethods.extension) {
|
||||
const nostr = this.getNostrObject()
|
||||
|
||||
if (!nostr.nip04) {
|
||||
throw new Error(
|
||||
`Your nostr extension does not support nip04 encryption & decryption`
|
||||
)
|
||||
}
|
||||
|
||||
const decrypted = await nostr.nip04.decrypt(sender, content)
|
||||
return decrypted
|
||||
}
|
||||
|
||||
if (loginMethod === LoginMethods.privateKey) {
|
||||
const keys = (store.getState().auth as AuthState).keyPair
|
||||
|
||||
if (!keys) {
|
||||
throw new Error(
|
||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
||||
)
|
||||
}
|
||||
|
||||
const { private: nsec } = keys
|
||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||
|
||||
const decrypted = await nip04.decrypt(privateKey, sender, content)
|
||||
return decrypted
|
||||
}
|
||||
|
||||
if (loginMethod === LoginMethods.nsecBunker) {
|
||||
const user = new NDKUser({ pubkey: sender })
|
||||
|
||||
this.remoteSigner?.on('authUrl', (authUrl) => {
|
||||
this.emit('nsecbunker-auth', authUrl)
|
||||
})
|
||||
|
||||
if (!this.remoteSigner) throw new Error('Remote signer is undefined.')
|
||||
const decrypted = await this.remoteSigner.decrypt(user, content)
|
||||
|
||||
return decrypted
|
||||
}
|
||||
|
||||
throw new Error('Login method is undefined')
|
||||
}
|
||||
|
||||
/**
|
||||
* Function will capture the public key from the nostr extension or if no extension present
|
||||
* function wil capture the public key from the local storage
|
||||
|
@ -33,6 +33,7 @@ import { Meta, ProfileMetadata, User, UserRole } from '../../types'
|
||||
import {
|
||||
encryptArrayBuffer,
|
||||
generateEncryptionKey,
|
||||
generateKeysFile,
|
||||
getHash,
|
||||
hexToNpub,
|
||||
isOnline,
|
||||
@ -49,15 +50,12 @@ import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import type { Identifier, XYCoord } from 'dnd-core'
|
||||
import { useDrag, useDrop } from 'react-dnd'
|
||||
import saveAs from 'file-saver'
|
||||
import CopyModal from '../../components/copyModal'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
|
||||
export const CreatePage = () => {
|
||||
const navigate = useNavigate()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
const [openCopyModal, setOpenCopyModel] = useState(false)
|
||||
const [textToCopy, setTextToCopy] = useState('')
|
||||
|
||||
const [authUrl, setAuthUrl] = useState<string>()
|
||||
|
||||
@ -374,26 +372,68 @@ export const CreatePage = () => {
|
||||
encryptionKey: string
|
||||
): Promise<ArrayBuffer> => {
|
||||
setLoadingSpinnerDesc('Encrypting zip file')
|
||||
return encryptArrayBuffer(arraybuffer, encryptionKey).finally(() =>
|
||||
setIsLoading(false)
|
||||
return encryptArrayBuffer(arraybuffer, encryptionKey)
|
||||
}
|
||||
|
||||
// create final zip file
|
||||
const createFinalZipFile = async (
|
||||
encryptedArrayBuffer: ArrayBuffer,
|
||||
encryptionKey: string
|
||||
): Promise<File | null> => {
|
||||
// Get the current timestamp in seconds
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
// Create a File object with the Blob data
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
|
||||
const firstSigner = users.filter((user) => user.role === UserRole.signer)[0]
|
||||
|
||||
const keysFileContent = await generateKeysFile(
|
||||
[firstSigner.pubkey],
|
||||
encryptionKey
|
||||
)
|
||||
if (!keysFileContent) return null
|
||||
|
||||
const zip = new JSZip()
|
||||
zip.file(`compressed.sigit`, file)
|
||||
zip.file('keys.json', keysFileContent)
|
||||
|
||||
const arraybuffer = await zip
|
||||
.generateAsync({
|
||||
type: 'arraybuffer',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 6 }
|
||||
})
|
||||
.catch(handleZipError)
|
||||
|
||||
if (!arraybuffer) return null
|
||||
|
||||
const finalZipFile = new File(
|
||||
[new Blob([arraybuffer])],
|
||||
`${unixNow}.sigit.zip`,
|
||||
{
|
||||
type: 'application/zip'
|
||||
}
|
||||
)
|
||||
|
||||
return finalZipFile
|
||||
}
|
||||
|
||||
// Handle file upload and further actions based on online/offline status
|
||||
const handleFileUpload = async (blob: Blob, encryptionKey: string) => {
|
||||
const handleFileUpload = async (file: File, arrayBuffer: ArrayBuffer) => {
|
||||
if (await isOnline()) {
|
||||
const fileUrl = await uploadFile(blob)
|
||||
const fileUrl = await uploadFile(file)
|
||||
|
||||
if (!fileUrl) return
|
||||
|
||||
await sendDMs(fileUrl, encryptionKey)
|
||||
await sendDMs(fileUrl)
|
||||
setIsLoading(false)
|
||||
|
||||
navigate(
|
||||
`${appPrivateRoutes.sign}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(encryptionKey)}`
|
||||
)
|
||||
navigate(appPrivateRoutes.sign, { state: { arrayBuffer } })
|
||||
} else {
|
||||
handleOffline(blob, encryptionKey)
|
||||
handleOffline(file, arrayBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,11 +446,11 @@ export const CreatePage = () => {
|
||||
}
|
||||
|
||||
// Upload the file to the storage and send DMs to signers/viewers
|
||||
const uploadFile = async (blob: Blob): Promise<string | null> => {
|
||||
const uploadFile = async (file: File): Promise<string | null> => {
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Uploading zip file to file storage.')
|
||||
|
||||
const fileUrl = await uploadToFileStorage(blob, nostrController)
|
||||
const fileUrl = await uploadToFileStorage(file, nostrController)
|
||||
.then((url) => {
|
||||
toast.success('zip file uploaded to file storage')
|
||||
return url
|
||||
@ -420,8 +460,8 @@ export const CreatePage = () => {
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
// Send DMs to signers and viewers with the file URL and encryption key
|
||||
const sendDMs = async (fileUrl: string, encryptionKey: string) => {
|
||||
// Send DMs to signers and viewers with the file URL
|
||||
const sendDMs = async (fileUrl: string) => {
|
||||
setLoadingSpinnerDesc('Sending DM to signers/viewers')
|
||||
|
||||
const signers = users.filter((user) => user.role === UserRole.signer)
|
||||
@ -430,7 +470,6 @@ export const CreatePage = () => {
|
||||
if (signers.length > 0) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
signers[0].pubkey,
|
||||
nostrController,
|
||||
true,
|
||||
@ -438,34 +477,15 @@ export const CreatePage = () => {
|
||||
)
|
||||
} else {
|
||||
for (const viewer of viewers) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
encryptionKey,
|
||||
viewer.pubkey,
|
||||
nostrController,
|
||||
false,
|
||||
setAuthUrl
|
||||
)
|
||||
await sendDM(fileUrl, viewer.pubkey, nostrController, false, setAuthUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage offline scenarios for signing or viewing the file
|
||||
const handleOffline = (blob: Blob, encryptionKey: string) => {
|
||||
const signers = users.filter((user) => user.role === UserRole.signer)
|
||||
|
||||
if (signers[0] && signers[0].pubkey === usersPubkey) {
|
||||
// Create a File object with the Blob data for offline signing
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } })
|
||||
} else {
|
||||
// Save the file and show encryption key for offline viewing
|
||||
saveAs(blob, 'request.sigit')
|
||||
setTextToCopy(encryptionKey)
|
||||
setOpenCopyModel(true)
|
||||
}
|
||||
const handleOffline = (file: File, arrayBuffer: ArrayBuffer) => {
|
||||
saveAs(file, 'request.sigit.zip')
|
||||
navigate(appPrivateRoutes.sign, { state: { arrayBuffer } })
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
@ -497,9 +517,15 @@ export const CreatePage = () => {
|
||||
arraybuffer,
|
||||
encryptionKey
|
||||
)
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
|
||||
return await handleFileUpload(blob, encryptionKey)
|
||||
const finalZipFile = await createFinalZipFile(
|
||||
encryptedArrayBuffer,
|
||||
encryptionKey
|
||||
)
|
||||
|
||||
if (!finalZipFile) return
|
||||
|
||||
return await handleFileUpload(finalZipFile, arraybuffer)
|
||||
}
|
||||
|
||||
if (authUrl) {
|
||||
@ -596,15 +622,6 @@ export const CreatePage = () => {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<CopyModal
|
||||
open={openCopyModal}
|
||||
handleClose={() => {
|
||||
setOpenCopyModel(false)
|
||||
navigate(appPrivateRoutes.sign)
|
||||
}}
|
||||
title="Decryption key for Sigit file"
|
||||
textToCopy={textToCopy}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme
|
||||
@ -52,7 +51,8 @@ import {
|
||||
shorten,
|
||||
signEventForMetaFile,
|
||||
uploadToFileStorage,
|
||||
isOnline
|
||||
isOnline,
|
||||
generateKeysFile
|
||||
} from '../../utils'
|
||||
import styles from './style.module.scss'
|
||||
import {
|
||||
@ -71,14 +71,13 @@ enum SignedStatus {
|
||||
export const SignPage = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { file, encryptionKey: encKey } = location.state || {}
|
||||
const { arrayBuffer: decryptedArrayBuffer } = location.state || {}
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
const [displayInput, setDisplayInput] = useState(false)
|
||||
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const [encryptionKey, setEncryptionKey] = useState('')
|
||||
|
||||
const [zip, setZip] = useState<JSZip>()
|
||||
|
||||
@ -194,9 +193,8 @@ export const SignPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fileUrl = searchParams.get('file')
|
||||
const key = searchParams.get('key')
|
||||
|
||||
if (fileUrl && key) {
|
||||
if (fileUrl) {
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Fetching file from file server')
|
||||
|
||||
@ -208,7 +206,7 @@ export const SignPage = () => {
|
||||
const fileName = fileUrl.split('/').pop()
|
||||
const file = new File([res.data], fileName!)
|
||||
|
||||
decrypt(file, decodeURIComponent(key)).then((arrayBuffer) => {
|
||||
decrypt(file).then((arrayBuffer) => {
|
||||
if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
|
||||
})
|
||||
})
|
||||
@ -221,40 +219,109 @@ export const SignPage = () => {
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
} else if (file && encKey) {
|
||||
decrypt(file, decodeURIComponent(encKey))
|
||||
.then((arrayBuffer) => {
|
||||
if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`error occurred in decryption`, err)
|
||||
toast.error(err.message || `error occurred in decryption`)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
} else if (decryptedArrayBuffer) {
|
||||
handleDecryptedArrayBuffer(decryptedArrayBuffer).finally(() =>
|
||||
setIsLoading(false)
|
||||
)
|
||||
} else {
|
||||
setIsLoading(false)
|
||||
setDisplayInput(true)
|
||||
}
|
||||
}, [searchParams, file, encKey])
|
||||
}, [searchParams, decryptedArrayBuffer])
|
||||
|
||||
const decrypt = async (file: File, key: string) => {
|
||||
const parseKeysJson = async (zip: JSZip) => {
|
||||
const keysFileContent = await readContentOfZipEntry(
|
||||
zip,
|
||||
'keys.json',
|
||||
'string'
|
||||
)
|
||||
|
||||
if (!keysFileContent) return null
|
||||
|
||||
const parsedJSON = await parseJson<{ sender: string; keys: string[] }>(
|
||||
keysFileContent
|
||||
).catch((err) => {
|
||||
console.log(`Error parsing content of keys.json:`, err)
|
||||
toast.error(err.message || `Error parsing content of keys.json`)
|
||||
return null
|
||||
})
|
||||
|
||||
return parsedJSON
|
||||
}
|
||||
|
||||
const decrypt = async (file: File) => {
|
||||
setLoadingSpinnerDesc('Decrypting file')
|
||||
|
||||
const encryptedArrayBuffer = await file.arrayBuffer()
|
||||
const zip = await JSZip.loadAsync(file).catch((err) => {
|
||||
console.log('err in loading zip file :>> ', err)
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
return null
|
||||
})
|
||||
if (!zip) return
|
||||
|
||||
const arrayBuffer = await decryptArrayBuffer(encryptedArrayBuffer, key)
|
||||
.catch((err) => {
|
||||
console.log('err in decryption:>> ', err)
|
||||
toast.error(err.message || 'An error occurred in decrypting file.')
|
||||
return null
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
const parsedKeysJson = await parseKeysJson(zip)
|
||||
if (!parsedKeysJson) return
|
||||
|
||||
const encryptedArrayBuffer = await readContentOfZipEntry(
|
||||
zip,
|
||||
'compressed.sigit',
|
||||
'arraybuffer'
|
||||
)
|
||||
|
||||
if (!encryptedArrayBuffer) return
|
||||
|
||||
const { keys, sender } = parsedKeysJson
|
||||
|
||||
for (const key of keys) {
|
||||
// Set up event listener for authentication event
|
||||
nostrController.on('nsecbunker-auth', (url) => {
|
||||
setAuthUrl(url)
|
||||
})
|
||||
|
||||
return arrayBuffer
|
||||
// Set up timeout promise to handle encryption timeout
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('Timeout occurred'))
|
||||
}, 60000) // Timeout duration = 60 seconds
|
||||
})
|
||||
|
||||
// decrypt the encryptionKey, with timeout
|
||||
const encryptionKey = await Promise.race([
|
||||
nostrController.nip04Decrypt(sender, key),
|
||||
timeoutPromise
|
||||
])
|
||||
.then((res) => {
|
||||
return res
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err :>> ', err)
|
||||
return null
|
||||
})
|
||||
.finally(() => {
|
||||
setAuthUrl(undefined) // Clear authentication URL
|
||||
})
|
||||
|
||||
console.log('encryptionKey :>> ', encryptionKey)
|
||||
|
||||
// Return if encryption failed
|
||||
if (!encryptionKey) continue
|
||||
|
||||
const arrayBuffer = await decryptArrayBuffer(
|
||||
encryptedArrayBuffer,
|
||||
encryptionKey
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log('err in decryption:>> ', err)
|
||||
return null
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
if (arrayBuffer) return arrayBuffer
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const handleDecryptedArrayBuffer = async (arrayBuffer: ArrayBuffer) => {
|
||||
@ -348,13 +415,10 @@ export const SignPage = () => {
|
||||
}
|
||||
|
||||
const handleDecrypt = async () => {
|
||||
if (!selectedFile || !encryptionKey) return
|
||||
if (!selectedFile) return
|
||||
|
||||
setIsLoading(true)
|
||||
const arrayBuffer = await decrypt(
|
||||
selectedFile,
|
||||
decodeURIComponent(encryptionKey)
|
||||
)
|
||||
const arrayBuffer = await decrypt(selectedFile)
|
||||
|
||||
if (!arrayBuffer) return
|
||||
|
||||
@ -407,13 +471,15 @@ export const SignPage = () => {
|
||||
setLoadingSpinnerDesc('Encrypting zip file')
|
||||
const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
|
||||
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)
|
||||
|
||||
if (!finalZipFile) return
|
||||
|
||||
if (await isOnline()) {
|
||||
await handleOnlineFlow(blob, key)
|
||||
} else {
|
||||
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
|
||||
await handleOnlineFlow(finalZipFile)
|
||||
}
|
||||
|
||||
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
|
||||
}
|
||||
|
||||
// Read the content of the hashes.json file
|
||||
@ -491,32 +557,101 @@ export const SignPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// create final zip file
|
||||
const createFinalZipFile = async (
|
||||
encryptedArrayBuffer: ArrayBuffer,
|
||||
encryptionKey: string
|
||||
): Promise<File | null> => {
|
||||
// Get the current timestamp in seconds
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
const blob = new Blob([encryptedArrayBuffer])
|
||||
// Create a File object with the Blob data
|
||||
const file = new File([blob], `compressed.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
|
||||
const isLastSigner = checkIsLastSigner(signers)
|
||||
|
||||
const userSet = new Set<string>()
|
||||
|
||||
if (isLastSigner) {
|
||||
if (submittedBy) {
|
||||
userSet.add(submittedBy)
|
||||
}
|
||||
|
||||
signers.forEach((signer) => {
|
||||
userSet.add(npubToHex(signer)!)
|
||||
})
|
||||
|
||||
viewers.forEach((viewer) => {
|
||||
userSet.add(npubToHex(viewer)!)
|
||||
})
|
||||
} else {
|
||||
const usersNpub = hexToNpub(usersPubkey!)
|
||||
const signerIndex = signers.indexOf(usersNpub)
|
||||
const nextSigner = signers[signerIndex + 1]
|
||||
userSet.add(npubToHex(nextSigner)!)
|
||||
}
|
||||
|
||||
const keysFileContent = await generateKeysFile(
|
||||
Array.from(userSet),
|
||||
encryptionKey
|
||||
)
|
||||
if (!keysFileContent) return null
|
||||
|
||||
const zip = new JSZip()
|
||||
zip.file(`compressed.sigit`, file)
|
||||
zip.file('keys.json', keysFileContent)
|
||||
|
||||
const arraybuffer = await zip
|
||||
.generateAsync({
|
||||
type: 'arraybuffer',
|
||||
compression: 'DEFLATE',
|
||||
compressionOptions: { level: 6 }
|
||||
})
|
||||
.catch(handleZipError)
|
||||
|
||||
if (!arraybuffer) return null
|
||||
|
||||
const finalZipFile = new File(
|
||||
[new Blob([arraybuffer])],
|
||||
`${unixNow}.sigit.zip`,
|
||||
{
|
||||
type: 'application/zip'
|
||||
}
|
||||
)
|
||||
|
||||
return finalZipFile
|
||||
}
|
||||
|
||||
// Handle errors during zip file generation
|
||||
const handleZipError = (err: any) => {
|
||||
console.log('Error in zip:>> ', err)
|
||||
setIsLoading(false)
|
||||
toast.error(err.message || 'Error occurred in generating zip file')
|
||||
return null
|
||||
}
|
||||
|
||||
// Handle the online flow: upload file and send DMs
|
||||
const handleOnlineFlow = async (blob: Blob, key: string) => {
|
||||
const fileUrl = await uploadZipFile(blob)
|
||||
const handleOnlineFlow = async (file: File) => {
|
||||
const fileUrl = await uploadZipFile(file)
|
||||
if (!fileUrl) return
|
||||
|
||||
const isLastSigner = checkIsLastSigner(signers)
|
||||
|
||||
if (isLastSigner) {
|
||||
await sendDMToAllUsers(fileUrl, key)
|
||||
await sendDMToAllUsers(fileUrl)
|
||||
} else {
|
||||
await sendDMToNextSigner(fileUrl, key)
|
||||
await sendDMToNextSigner(fileUrl)
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
// Update search params with updated file URL and encryption key
|
||||
setSearchParams({
|
||||
file: fileUrl,
|
||||
key: key
|
||||
})
|
||||
}
|
||||
|
||||
// Upload the zip file to file storage
|
||||
const uploadZipFile = async (blob: Blob): Promise<string | null> => {
|
||||
const uploadZipFile = async (file: File): Promise<string | null> => {
|
||||
setLoadingSpinnerDesc('Uploading zip file to file storage.')
|
||||
const fileUrl = await uploadToFileStorage(blob, nostrController)
|
||||
const fileUrl = await uploadToFileStorage(file, nostrController)
|
||||
.then((url) => {
|
||||
toast.success('Zip file uploaded to file storage')
|
||||
return url
|
||||
@ -540,7 +675,7 @@ export const SignPage = () => {
|
||||
}
|
||||
|
||||
// Send DM to all users (signers and viewers)
|
||||
const sendDMToAllUsers = async (fileUrl: string, key: string) => {
|
||||
const sendDMToAllUsers = async (fileUrl: string) => {
|
||||
const userSet = new Set<`npub1${string}`>()
|
||||
|
||||
if (submittedBy) {
|
||||
@ -560,7 +695,6 @@ export const SignPage = () => {
|
||||
for (const user of users) {
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
key,
|
||||
npubToHex(user)!,
|
||||
nostrController,
|
||||
false,
|
||||
@ -570,13 +704,12 @@ export const SignPage = () => {
|
||||
}
|
||||
|
||||
// Send DM to the next signer
|
||||
const sendDMToNextSigner = async (fileUrl: string, key: string) => {
|
||||
const sendDMToNextSigner = async (fileUrl: string) => {
|
||||
const usersNpub = hexToNpub(usersPubkey!)
|
||||
const signerIndex = signers.indexOf(usersNpub)
|
||||
const nextSigner = signers[signerIndex + 1]
|
||||
await sendDM(
|
||||
fileUrl,
|
||||
key,
|
||||
npubToHex(nextSigner)!,
|
||||
nostrController,
|
||||
true,
|
||||
@ -771,18 +904,9 @@ export const SignPage = () => {
|
||||
value={selectedFile}
|
||||
onChange={(value) => setSelectedFile(value)}
|
||||
/>
|
||||
|
||||
{selectedFile && (
|
||||
<TextField
|
||||
label="Encryption Key"
|
||||
variant="outlined"
|
||||
value={encryptionKey}
|
||||
onChange={(e) => setEncryptionKey(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{selectedFile && encryptionKey && (
|
||||
{selectedFile && (
|
||||
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
|
||||
<Button onClick={handleDecrypt} variant="contained">
|
||||
Decrypt
|
||||
|
@ -1,5 +1,10 @@
|
||||
import axios from 'axios'
|
||||
import { EventTemplate } from 'nostr-tools'
|
||||
import {
|
||||
EventTemplate,
|
||||
generateSecretKey,
|
||||
getPublicKey,
|
||||
nip04
|
||||
} from 'nostr-tools'
|
||||
import { MetadataController, NostrController } from '../controllers'
|
||||
import { toast } from 'react-toastify'
|
||||
import { appPrivateRoutes } from '../routes'
|
||||
@ -11,17 +16,12 @@ import { appPrivateRoutes } from '../routes'
|
||||
* @returns The URL of the uploaded file.
|
||||
*/
|
||||
export const uploadToFileStorage = async (
|
||||
blob: Blob,
|
||||
file: File,
|
||||
nostrController: NostrController
|
||||
) => {
|
||||
// Get the current timestamp in seconds
|
||||
const unixNow = Math.floor(Date.now() / 1000)
|
||||
|
||||
// Create a File object with the Blob data
|
||||
const file = new File([blob], `compressed-${unixNow}.sigit`, {
|
||||
type: 'application/sigit'
|
||||
})
|
||||
|
||||
// Define event metadata for authorization
|
||||
const event: EventTemplate = {
|
||||
kind: 24242,
|
||||
@ -56,7 +56,6 @@ export const uploadToFileStorage = async (
|
||||
/**
|
||||
* Sends a Direct Message (DM) to a recipient, encrypting the content and handling authentication.
|
||||
* @param fileUrl The URL of the encrypted zip file to be included in the DM.
|
||||
* @param encryptionKey The encryption key used to decrypt the zip file to be included in the DM.
|
||||
* @param pubkey The public key of the recipient.
|
||||
* @param nostrController The NostrController instance for handling authentication and encryption.
|
||||
* @param isSigner Boolean indicating whether the recipient is a signer or viewer.
|
||||
@ -64,7 +63,6 @@ export const uploadToFileStorage = async (
|
||||
*/
|
||||
export const sendDM = async (
|
||||
fileUrl: string,
|
||||
encryptionKey: string,
|
||||
pubkey: string,
|
||||
nostrController: NostrController,
|
||||
isSigner: boolean,
|
||||
@ -77,9 +75,7 @@ export const sendDM = async (
|
||||
|
||||
const decryptionUrl = `${window.location.origin}/#${
|
||||
appPrivateRoutes.sign
|
||||
}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(
|
||||
encryptionKey
|
||||
)}`
|
||||
}?file=${encodeURIComponent(fileUrl)}`
|
||||
|
||||
const content = `${initialLine}\n\n${decryptionUrl}`
|
||||
|
||||
@ -205,3 +201,52 @@ export const signEventForMetaFile = async (
|
||||
|
||||
return signedEvent // Return the signed event
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the content for keys.json file.
|
||||
*
|
||||
* @param users - An array of public keys.
|
||||
* @param key - The key that will be encrypted for each user.
|
||||
* @returns A promise that resolves to a JSON string containing the sender's public key and encrypted keys, or null if an error occurs.
|
||||
*/
|
||||
export const generateKeysFile = async (
|
||||
users: string[],
|
||||
key: string
|
||||
): Promise<string | null> => {
|
||||
// Generate a random private key to act as the sender
|
||||
const privateKey = generateSecretKey()
|
||||
|
||||
// Calculate the required length to be a multiple of 10
|
||||
const requiredLength = Math.ceil(users.length / 10) * 10
|
||||
const additionalKeysCount = requiredLength - users.length
|
||||
|
||||
if (additionalKeysCount > 0) {
|
||||
// generate random public keys to make the keys array multiple of 10
|
||||
const additionalPubkeys = Array.from({ length: additionalKeysCount }, () =>
|
||||
getPublicKey(generateSecretKey())
|
||||
)
|
||||
|
||||
users.push(...additionalPubkeys)
|
||||
}
|
||||
|
||||
// Encrypt the key for each user's public key
|
||||
const promises = users.map((pubkey) => nip04.encrypt(privateKey, pubkey, key))
|
||||
|
||||
// Wait for all encryption promises to resolve
|
||||
const keys = await Promise.all(promises).catch((err) => {
|
||||
console.log('Error while generating keys :>> ', err)
|
||||
toast.error(err.message || 'An error occurred while generating key')
|
||||
return null
|
||||
})
|
||||
|
||||
// If any encryption promise failed, return null
|
||||
if (!keys) return null
|
||||
|
||||
try {
|
||||
// Return a JSON string containing the sender's public key and encrypted keys
|
||||
return JSON.stringify({ sender: getPublicKey(privateKey), keys })
|
||||
} catch (error) {
|
||||
// Return null if an error occurs during JSON stringification
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user