issue-166-open-timestamps #220
@ -5,7 +5,13 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { NostrController } from '../../controllers'
|
import { NostrController } from '../../controllers'
|
||||||
import { DocSignatureEvent, Meta } from '../../types'
|
import {
|
||||||
|
DocSignatureEvent,
|
||||||
|
Meta,
|
||||||
|
SignedEvent,
|
||||||
|
Timestamp,
|
||||||
|
TimestampUpgradeVerifyResponse
|
||||||
|
} from '../../types'
|
||||||
import {
|
import {
|
||||||
decryptArrayBuffer,
|
decryptArrayBuffer,
|
||||||
extractMarksFromSignedMeta,
|
extractMarksFromSignedMeta,
|
||||||
@ -15,7 +21,10 @@ import {
|
|||||||
parseJson,
|
parseJson,
|
||||||
readContentOfZipEntry,
|
readContentOfZipEntry,
|
||||||
signEventForMetaFile,
|
signEventForMetaFile,
|
||||||
getCurrentUserFiles
|
getCurrentUserFiles,
|
||||||
|
updateUsersAppData,
|
||||||
|
npubToHex,
|
||||||
|
sendNotification
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
@ -48,6 +57,12 @@ import {
|
|||||||
faFile,
|
faFile,
|
||||||
faFileDownload
|
faFileDownload
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import {
|
||||||
|
upgradeAndVerifyTimestamp,
|
||||||
|
upgradeTimestamps,
|
||||||
|
verifyTimestamps
|
||||||
|
} from '../../utils/opentimestamps.ts'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
interface PdfViewProps {
|
interface PdfViewProps {
|
||||||
files: CurrentUserFile[]
|
files: CurrentUserFile[]
|
||||||
@ -180,7 +195,8 @@ export const VerifyPage = () => {
|
|||||||
signers,
|
signers,
|
||||||
viewers,
|
viewers,
|
||||||
fileHashes,
|
fileHashes,
|
||||||
parsedSignatureEvents
|
parsedSignatureEvents,
|
||||||
|
timestamps
|
||||||
} = useSigitMeta(meta)
|
} = useSigitMeta(meta)
|
||||||
|
|
||||||
const [files, setFiles] = useState<{ [filename: string]: SigitFile }>({})
|
const [files, setFiles] = useState<{ [filename: string]: SigitFile }>({})
|
||||||
@ -190,6 +206,16 @@ export const VerifyPage = () => {
|
|||||||
[key: string]: string | null
|
[key: string]: string | null
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
|
const signTimestampEvent = async (signerContent: {
|
||||||
|
timestamps: Timestamp[]
|
||||||
|
}): Promise<SignedEvent | null> => {
|
||||||
|
return await signEventForMetaFile(
|
||||||
|
JSON.stringify(signerContent),
|
||||||
|
nostrController,
|
||||||
|
setIsLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.entries(files).length > 0) {
|
if (Object.entries(files).length > 0) {
|
||||||
const tmp = getCurrentUserFiles(files, currentFileHashes, fileHashes)
|
const tmp = getCurrentUserFiles(files, currentFileHashes, fileHashes)
|
||||||
@ -198,6 +224,89 @@ export const VerifyPage = () => {
|
|||||||
}, [currentFileHashes, fileHashes, files])
|
}, [currentFileHashes, fileHashes, files])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (timestamps && timestamps.length > 0) {
|
||||||
|
if (timestamps.every((t) => !!t.verification)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const upgradeT = async (timestamps: Timestamp[]) => {
|
||||||
|
const verifiedResults = await Promise.all(
|
||||||
|
timestamps.map(upgradeAndVerifyTimestamp)
|
||||||
|
)
|
||||||
|
|
||||||
|
const upgradedTimestamps = verifiedResults
|
||||||
|
.filter((t) => t.upgraded || isNewlyVerified(t, timestamps))
|
||||||
|
.map((t) => {
|
||||||
|
const timestamp = t.value
|
||||||
|
if (t.verified) {
|
||||||
|
timestamp.verification = t.verification
|
||||||
|
}
|
||||||
|
return timestamp
|
||||||
|
})
|
||||||
|
|
||||||
|
const isNewlyVerified = (
|
||||||
|
upgradedTimestamp: TimestampUpgradeVerifyResponse,
|
||||||
|
timestamps: Timestamp[]
|
||||||
|
) => {
|
||||||
|
if (!upgradedTimestamp.verified) return false
|
||||||
|
const oldT = timestamps.find(
|
||||||
|
(t) => t.nostrId === upgradedTimestamp.value.nostrId
|
||||||
|
)
|
||||||
|
if (!oldT) return false
|
||||||
|
if (!oldT.verification && upgradedTimestamp.verified) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedEvent = await signTimestampEvent({
|
||||||
|
timestamps: upgradedTimestamps
|
||||||
|
})
|
||||||
|
if (!signedEvent) return
|
||||||
|
|
||||||
|
const finalTimestamps = timestamps.map((t) => {
|
||||||
|
const upgraded = upgradedTimestamps.find(
|
||||||
|
(tu) => tu.nostrId === t.nostrId
|
||||||
|
)
|
||||||
|
if (upgraded) {
|
||||||
|
return {
|
||||||
|
...upgraded,
|
||||||
|
signature: JSON.stringify(signedEvent, null, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
})
|
||||||
|
|
||||||
|
const updatedMeta = _.cloneDeep(meta)
|
||||||
|
updatedMeta.timestamps = [...finalTimestamps]
|
||||||
|
updatedMeta.modifiedAt = unixNow()
|
||||||
|
|
||||||
|
const updatedEvent = await updateUsersAppData(updatedMeta)
|
||||||
|
if (!updatedEvent) return
|
||||||
|
|
||||||
|
const userSet = new Set<`npub1${string}`>()
|
||||||
|
signers.forEach((signer) => {
|
||||||
|
if (signer !== usersPubkey) {
|
||||||
|
userSet.add(signer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
viewers.forEach((viewer) => {
|
||||||
|
userSet.add(viewer)
|
||||||
|
})
|
||||||
|
|
||||||
|
const users = Array.from(userSet)
|
||||||
|
const promises = users.map((user) =>
|
||||||
|
sendNotification(npubToHex(user)!, updatedMeta)
|
||||||
|
)
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
setTimestamps(finalTimestamps)
|
||||||
|
setMeta(meta)
|
||||||
|
}
|
||||||
|
upgradeT(timestamps)
|
||||||
|
}
|
||||||
|
}, [timestamps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('this runs')
|
||||||
if (metaInNavState && encryptionKey) {
|
if (metaInNavState && encryptionKey) {
|
||||||
const processSigit = async () => {
|
const processSigit = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
@ -43,14 +43,20 @@ export interface Sigit {
|
|||||||
export interface Timestamp {
|
export interface Timestamp {
|
||||||
nostrId: string
|
nostrId: string
|
||||||
timestamp: string
|
timestamp: string
|
||||||
isComplete?: boolean
|
verification?: TimestampVerification
|
||||||
|
signature?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimestampUpgradeResponse {
|
export interface TimestampVerification {
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimestampUpgradeVerifyResponse {
|
||||||
|
value: Timestamp
|
||||||
upgraded: boolean
|
upgraded: boolean
|
||||||
nostrId: string
|
isComplete?: boolean
|
||||||
timestamp: string
|
verified?: boolean
|
||||||
isComplete: boolean
|
verification?: TimestampVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserAppData {
|
export interface UserAppData {
|
||||||
|
9
src/types/opentimestamps.d.ts
vendored
9
src/types/opentimestamps.d.ts
vendored
@ -10,7 +10,10 @@ interface OpenTimestamps {
|
|||||||
stamp(file: any): Promise<void>
|
stamp(file: any): Promise<void>
|
||||||
|
|
||||||
// Verify the provided timestamp proof file
|
// Verify the provided timestamp proof file
|
||||||
verify(ots: string, file: string): Promise<void>
|
verify(
|
||||||
|
ots: string,
|
||||||
|
file: string
|
||||||
|
): Promise<TimestampVerficiationResponse | Record<string, never>>
|
||||||
|
|
||||||
// Other utilities or operations (like OpSHA256, serialization)
|
// Other utilities or operations (like OpSHA256, serialization)
|
||||||
Ops: {
|
Ops: {
|
||||||
@ -28,3 +31,7 @@ interface OpenTimestamps {
|
|||||||
// Other potential methods based on repo functions
|
// Other potential methods based on repo functions
|
||||||
upgrade(file: any): Promise<boolean>
|
upgrade(file: any): Promise<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TimestampVerficiationResponse {
|
||||||
|
bitcoin: { timestamp: number; height: number }
|
||||||
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { Timestamp } from '../types'
|
import {
|
||||||
import { retry } from './retry.ts'
|
Timestamp,
|
||||||
|
TimestampUpgradeResponse,
|
||||||
|
TimestampUpgradeVerifyResponse
|
||||||
|
} from '../types'
|
||||||
|
import { retry, retryAll } from './retry.ts'
|
||||||
import { bytesToHex } from '@noble/hashes/utils'
|
import { bytesToHex } from '@noble/hashes/utils'
|
||||||
import { utf8Encoder } from 'nostr-tools/utils'
|
import { utf8Encoder } from 'nostr-tools/utils'
|
||||||
|
import { hexStringToUint8Array } from './string.ts'
|
||||||
|
import { isPromiseFulfilled } from './utils.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a timestamp for the provided nostr event ID.
|
* Generates a timestamp for the provided nostr event ID.
|
||||||
@ -21,6 +27,87 @@ export const generateTimestamp = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const upgradeAndVerifyTimestamp = async (
|
||||||
|
timestamp: Timestamp
|
||||||
|
): Promise<TimestampUpgradeVerifyResponse> => {
|
||||||
|
const upgradedResult = await retry(() => upgrade(timestamp))
|
||||||
|
const verifiedResult = await verify(upgradedResult)
|
||||||
|
|
||||||
|
console.log('verifiedResult: ', verifiedResult)
|
||||||
|
|
||||||
|
return verifiedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
export const upgradeTimestamps = async (
|
||||||
|
timestamps: Timestamp[]
|
||||||
|
): Promise<TimestampUpgradeResponse[]> => {
|
||||||
|
const resolved = await retryAll(timestamps.map((t) => () => upgrade(t)))
|
||||||
|
return resolved.filter(isPromiseFulfilled).map((res) => res.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verifyTimestamps = async (timestamps: Timestamp[]) => {
|
||||||
|
const settledResults = await Promise.allSettled(
|
||||||
|
timestamps.map(async (timestamp) => verify(timestamp))
|
||||||
|
)
|
||||||
|
return settledResults.filter(isPromiseFulfilled).map((res) => res.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgrade = async (
|
||||||
|
t: Timestamp
|
||||||
|
): Promise<TimestampUpgradeVerifyResponse> => {
|
||||||
|
let detachedTimestamp =
|
||||||
|
window.OpenTimestamps.DetachedTimestampFile.deserialize(
|
||||||
|
hexStringToUint8Array(t.timestamp)
|
||||||
|
)
|
||||||
|
|
||||||
|
const changed: boolean =
|
||||||
|
await window.OpenTimestamps.upgrade(detachedTimestamp)
|
||||||
|
if (changed) {
|
||||||
|
const updated = detachedTimestamp.serializeToBytes()
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
...t,
|
||||||
|
timestamp: bytesToHex(updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
upgraded: true,
|
||||||
|
isComplete: detachedTimestamp.timestamp.isTimestampComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: t,
|
||||||
|
upgraded: false,
|
||||||
|
isComplete: detachedTimestamp.timestamp.isTimestampComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verify = async (
|
||||||
|
t: TimestampUpgradeVerifyResponse
|
||||||
|
): Promise<TimestampUpgradeVerifyResponse> => {
|
||||||
|
const detachedNostrId = window.OpenTimestamps.DetachedTimestampFile.fromBytes(
|
||||||
|
new window.OpenTimestamps.Ops.OpSHA256(),
|
||||||
|
utf8Encoder.encode(t.value.nostrId)
|
||||||
|
)
|
||||||
|
|
||||||
|
let detachedTimestamp =
|
||||||
|
window.OpenTimestamps.DetachedTimestampFile.deserialize(
|
||||||
|
hexStringToUint8Array(t.value.timestamp)
|
||||||
|
)
|
||||||
|
|
||||||
|
const res = await window.OpenTimestamps.verify(
|
||||||
|
detachedTimestamp,
|
||||||
|
detachedNostrId
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...t,
|
||||||
|
verified: !!res.bitcoin,
|
||||||
|
verification: res?.bitcoin || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = async (nostrId: string): Promise<string> => {
|
const timestamp = async (nostrId: string): Promise<string> => {
|
||||||
const detachedTimestamp =
|
const detachedTimestamp =
|
||||||
window.OpenTimestamps.DetachedTimestampFile.fromBytes(
|
window.OpenTimestamps.DetachedTimestampFile.fromBytes(
|
||||||
|
Loading…
Reference in New Issue
Block a user