issue-166-open-timestamps #220

Merged
eugene merged 25 commits from issue-166-open-timestamps into staging 2024-10-25 11:18:47 +00:00
4 changed files with 220 additions and 11 deletions
Showing only changes of commit 21aa25a42a - Show all commits

View File

@ -5,7 +5,13 @@ import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { NostrController } from '../../controllers'
import { DocSignatureEvent, Meta } from '../../types'
import {
DocSignatureEvent,
Meta,
SignedEvent,
Timestamp,
TimestampUpgradeVerifyResponse
} from '../../types'
import {
decryptArrayBuffer,
extractMarksFromSignedMeta,
@ -15,7 +21,10 @@ import {
parseJson,
readContentOfZipEntry,
signEventForMetaFile,
getCurrentUserFiles
getCurrentUserFiles,
updateUsersAppData,
npubToHex,
sendNotification
} from '../../utils'
import styles from './style.module.scss'
import { useLocation } from 'react-router-dom'
@ -48,6 +57,12 @@ import {
faFile,
faFileDownload
} from '@fortawesome/free-solid-svg-icons'
import {
upgradeAndVerifyTimestamp,
upgradeTimestamps,
verifyTimestamps
} from '../../utils/opentimestamps.ts'
import _ from 'lodash'
interface PdfViewProps {
files: CurrentUserFile[]
@ -180,7 +195,8 @@ export const VerifyPage = () => {
signers,
viewers,
fileHashes,
parsedSignatureEvents
parsedSignatureEvents,
timestamps
} = useSigitMeta(meta)
const [files, setFiles] = useState<{ [filename: string]: SigitFile }>({})
@ -190,6 +206,16 @@ export const VerifyPage = () => {
[key: string]: string | null
}>({})
const signTimestampEvent = async (signerContent: {
timestamps: Timestamp[]
}): Promise<SignedEvent | null> => {
return await signEventForMetaFile(
JSON.stringify(signerContent),
nostrController,
setIsLoading
)
}
useEffect(() => {
if (Object.entries(files).length > 0) {
const tmp = getCurrentUserFiles(files, currentFileHashes, fileHashes)
@ -198,6 +224,89 @@ export const VerifyPage = () => {
}, [currentFileHashes, fileHashes, files])
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) {
const processSigit = async () => {
setIsLoading(true)

View File

@ -43,14 +43,20 @@ export interface Sigit {
export interface Timestamp {
nostrId: string
timestamp: string
isComplete?: boolean
verification?: TimestampVerification
signature?: string
}
export interface TimestampUpgradeResponse {
export interface TimestampVerification {
height: number
}
export interface TimestampUpgradeVerifyResponse {
value: Timestamp
upgraded: boolean
nostrId: string
timestamp: string
isComplete: boolean
isComplete?: boolean
verified?: boolean
verification?: TimestampVerification
}
export interface UserAppData {

View File

@ -10,7 +10,10 @@ interface OpenTimestamps {
stamp(file: any): Promise<void>
// 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)
Ops: {
@ -28,3 +31,7 @@ interface OpenTimestamps {
// Other potential methods based on repo functions
upgrade(file: any): Promise<boolean>
}
interface TimestampVerficiationResponse {
bitcoin: { timestamp: number; height: number }
}

View File

@ -1,7 +1,13 @@
import { Timestamp } from '../types'
import { retry } from './retry.ts'
import {
Timestamp,
TimestampUpgradeResponse,
TimestampUpgradeVerifyResponse
} from '../types'
import { retry, retryAll } from './retry.ts'
import { bytesToHex } from '@noble/hashes/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.
@ -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 detachedTimestamp =
window.OpenTimestamps.DetachedTimestampFile.fromBytes(