use own class for buffer/string hex handling

This commit is contained in:
einhornimmond 2025-10-13 17:12:03 +02:00
parent a31ba3756a
commit 49010af54f
10 changed files with 157 additions and 76 deletions

View File

@ -2,15 +2,14 @@ import {
CommunityHandshakeState as DbCommunityHandshakeState,
CommunityHandshakeStateLoggingView,
CommunityLoggingView,
Community as DbCommunity,
FederatedCommunity as DbFederatedCommunity,
FederatedCommunityLoggingView,
findPendingCommunityHandshake,
getHomeCommunityWithFederatedCommunityOrFail,
CommunityHandshakeStateType
CommunityHandshakeStateType,
getCommunityByPublicKeyOrFail
} from 'database'
import { randombytes_random } from 'sodium-native'
import { CONFIG as CONFIG_CORE } from 'core'
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
import { ensureUrlEndsWithSlash } from 'core'
@ -22,6 +21,7 @@ import { AuthenticationClientFactory } from './client/AuthenticationClientFactor
import { EncryptedTransferArgs } from 'core'
import { CommunityHandshakeStateLogic } from 'core'
import { CommunityLogic } from 'core'
import { Ed25519PublicKey } from 'shared'
const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`)
@ -38,7 +38,8 @@ export async function startCommunityAuthentication(
methodLogger.debug('homeComA', new CommunityLoggingView(homeComA))
const homeComALogic = new CommunityLogic(homeComA)
const homeFedComA = homeComALogic.getFederatedCommunityWithApiOrFail(fedComB.apiVersion)
const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey })
const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey)
const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey)
methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
// check if communityUuid is not a valid v4Uuid
@ -54,7 +55,7 @@ export async function startCommunityAuthentication(
)
// check if a authentication is already in progress
const existingState = await findPendingCommunityHandshake(fedComB.publicKey, fedComB.apiVersion, false)
const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false)
if (existingState) {
const stateLogic = new CommunityHandshakeStateLogic(existingState)
// retry on timeout or failure
@ -72,7 +73,7 @@ export async function startCommunityAuthentication(
throw new Error(`Public JWT key still not exist for comB ${comB.name}`)
}
const state = new DbCommunityHandshakeState()
state.publicKey = fedComB.publicKey
state.publicKey = fedComBPublicKey.asBuffer()
state.apiVersion = fedComB.apiVersion
state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
state.handshakeId = parseInt(handshakeID)
@ -87,7 +88,8 @@ export async function startCommunityAuthentication(
methodLogger.debug('jws', jws)
// prepare the args for the client invocation
const args = new EncryptedTransferArgs()
args.publicKey = homeComA!.publicKey.toString('hex')
const homeComAPublicKey = new Ed25519PublicKey(homeComA!.publicKey)
args.publicKey = homeComAPublicKey.asHex()
args.jwt = jws
args.handshakeID = handshakeID
await stateSaveResolver

View File

@ -12,7 +12,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
import { LogError } from '@/server/LogError'
import { createKeyPair, uint32Schema } from 'shared'
import { buffer32Schema, createKeyPair, Ed25519PublicKey, hex64Schema, uint32Schema } from 'shared'
import { getLogger } from 'log4js'
import { startCommunityAuthentication } from './authenticateCommunities'
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
@ -67,8 +67,11 @@ export async function validateCommunities(): Promise<void> {
const client = FederationClientFactory.getInstance(dbFedComB)
if (client instanceof V1_0_FederationClient) {
const pubKey = await client.getPublicKey()
if (pubKey && pubKey === dbFedComB.publicKey.toString('hex')) {
// throw if key isn't valid hex with length 64
const clientPublicKey = new Ed25519PublicKey(await client.getPublicKey())
// throw if key isn't valid hex with length 64
const fedComBPublicKey = new Ed25519PublicKey(dbFedComB.publicKey)
if (clientPublicKey.isSame(fedComBPublicKey)) {
await DbFederatedCommunity.update({ id: dbFedComB.id }, { verifiedAt: new Date() })
logger.debug(`verified dbFedComB with:`, dbFedComB.endPoint)
const pubComInfo = await client.getPublicCommunityInfo()
@ -84,7 +87,7 @@ export async function validateCommunities(): Promise<void> {
logger.debug('missing result of getPublicCommunityInfo')
}
} else {
logger.debug('received not matching publicKey:', pubKey, dbFedComB.publicKey.toString('hex'))
logger.debug('received not matching publicKey:', clientPublicKey.asHex(), fedComBPublicKey.asHex())
}
}
} catch (err) {

View File

@ -1,6 +1,6 @@
import { FindOptionsOrder, FindOptionsWhere, IsNull, MoreThanOrEqual, Not } from 'typeorm'
import { Community as DbCommunity } from '../entity'
import { urlSchema, uuidv4Schema } from 'shared'
import { Ed25519PublicKey, urlSchema, uuidv4Schema } from 'shared'
/**
* Retrieves the home community, i.e., a community that is not foreign.
@ -50,15 +50,21 @@ export async function getCommunityWithFederatedCommunityByIdentifier(
}
export async function getCommunityWithFederatedCommunityWithApiOrFail(
publicKey: Buffer,
publicKey: Ed25519PublicKey,
apiVersion: string
): Promise<DbCommunity> {
return await DbCommunity.findOneOrFail({
where: { foreign: true, publicKey, federatedCommunities: { apiVersion } },
where: { foreign: true, publicKey: publicKey.asBuffer(), federatedCommunities: { apiVersion } },
relations: { federatedCommunities: true },
})
}
export async function getCommunityByPublicKeyOrFail(publicKey: Ed25519PublicKey): Promise<DbCommunity> {
return await DbCommunity.findOneOrFail({
where: { publicKey: publicKey.asBuffer() },
})
}
// returns all reachable communities
// home community and all federated communities which have been verified within the last authenticationTimeoutMs
export async function getReachableCommunities(

View File

@ -1,5 +1,6 @@
import { Not, In } from 'typeorm'
import { CommunityHandshakeState, CommunityHandshakeStateType} from '..'
import { Ed25519PublicKey } from 'shared'
/**
* Find a pending community handshake by public key.
@ -8,11 +9,11 @@ import { CommunityHandshakeState, CommunityHandshakeStateType} from '..'
* @returns The CommunityHandshakeState with associated federated community and community.
*/
export function findPendingCommunityHandshake(
publicKey: Buffer, apiVersion: string, withRelations = true
publicKey: Ed25519PublicKey, apiVersion: string, withRelations = true
): Promise<CommunityHandshakeState | null> {
return CommunityHandshakeState.findOne({
where: {
publicKey,
publicKey: publicKey.asBuffer(),
apiVersion,
status: Not(In([
CommunityHandshakeStateType.EXPIRED,

View File

@ -6,17 +6,16 @@ import {
CommunityHandshakeStateLoggingView,
CommunityHandshakeState as DbCommunityHandshakeState,
CommunityHandshakeStateType,
Community as DbCommunity,
FederatedCommunity as DbFedCommunity,
FederatedCommunityLoggingView,
getHomeCommunity,
findPendingCommunityHandshake,
findPendingCommunityHandshakeOrFailByOneTimeCode,
} from 'database'
import { getLogger } from 'log4js'
import {
AuthenticationJwtPayloadType,
AuthenticationResponseJwtPayloadType,
Ed25519PublicKey,
encryptAndSign,
OpenConnectionCallbackJwtPayloadType,
OpenConnectionJwtPayloadType,
@ -39,44 +38,40 @@ export class AuthenticationResolver {
const methodLogger = createLogger('openConnection')
methodLogger.addContext('handshakeID', args.handshakeID)
methodLogger.debug(`openConnection() via apiVersion=1_0:`, args)
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
try {
const openConnectionJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionJwtPayloadType
methodLogger.debug('openConnectionJwtPayload', openConnectionJwtPayload)
if (!openConnectionJwtPayload) {
const errmsg = `invalid OpenConnection payload of requesting community with publicKey` + args.publicKey
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`invalid OpenConnection payload of requesting community with publicKey ${argsPublicKey.asHex()}`)
}
if (openConnectionJwtPayload.tokentype !== OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE) {
const errmsg = `invalid tokentype of community with publicKey` + args.publicKey
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`invalid tokentype of community with publicKey ${argsPublicKey.asHex()}`)
}
if (!openConnectionJwtPayload.url) {
const errmsg = `invalid url of community with publicKey` + args.publicKey
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`)
}
methodLogger.debug(`vor DbFedCommunity.findOneByOrFail()...`, { publicKey: args.publicKey })
const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: Buffer.from(args.publicKey, 'hex') })
methodLogger.debug(`vor DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() })
const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: argsPublicKey.asBuffer() })
methodLogger.debug(`nach DbFedCommunity.findOneByOrFail()...`, fedComA)
methodLogger.debug('fedComA', new FederatedCommunityLoggingView(fedComA))
if (!openConnectionJwtPayload.url.startsWith(fedComA.endPoint)) {
const errmsg = `invalid url of community with publicKey` + args.publicKey
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`)
}
// no await to respond immediately and invoke callback-request asynchronously
void startOpenConnectionCallback(args.handshakeID, args.publicKey, CONFIG.FEDERATION_API)
void startOpenConnectionCallback(args.handshakeID, argsPublicKey, CONFIG.FEDERATION_API)
methodLogger.debug('openConnection() successfully initiated callback and returns true immediately...')
return true
} catch (err) {
methodLogger.error('invalid jwt token:', err)
let errorText = ''
if (err instanceof Error) {
errorText = err.message
} else {
errorText = String(err)
}
methodLogger.error('invalid jwt token:', errorText)
// no infos to the caller
return true
}
}
@ -93,19 +88,13 @@ export class AuthenticationResolver {
// decrypt args.url with homeCom.privateJwtKey and verify signing with callbackFedCom.publicKey
const openConnectionCallbackJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionCallbackJwtPayloadType
if (!openConnectionCallbackJwtPayload) {
const errmsg = `invalid OpenConnectionCallback payload of requesting community with publicKey` + args.publicKey
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`invalid OpenConnectionCallback payload of requesting community with publicKey ${args.publicKey}`)
}
const { endPoint, apiVersion } = splitUrlInEndPointAndApiVersion(openConnectionCallbackJwtPayload.url)
methodLogger.debug(`search fedComB per:`, endPoint, apiVersion)
const fedComB = await DbFedCommunity.findOneBy({ endPoint, apiVersion })
if (!fedComB) {
const errmsg = `unknown callback community with url` + openConnectionCallbackJwtPayload.url
methodLogger.error(errmsg)
// no infos to the caller
return true
throw new Error(`unknown callback community with url ${openConnectionCallbackJwtPayload.url}`)
}
methodLogger.debug(
`found fedComB and start authentication:`,
@ -116,7 +105,14 @@ export class AuthenticationResolver {
methodLogger.debug('openConnectionCallback() successfully initiated authentication and returns true immediately...')
return true
} catch (err) {
methodLogger.error('invalid jwt token:', err)
let errorText = ''
if (err instanceof Error) {
errorText = err.message
} else {
errorText = String(err)
}
methodLogger.error('invalid jwt token:', errorText)
// no infos to the caller
return true
}
}
@ -131,22 +127,26 @@ export class AuthenticationResolver {
methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args)
let state: DbCommunityHandshakeState | null = null
let stateSaveResolver: Promise<DbCommunityHandshakeState> | undefined = undefined
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
try {
const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType
methodLogger.debug(`interpreted authentication payload...authArgs:`, authArgs)
if (!authArgs) {
throw new Error(`invalid authentication payload of requesting community with publicKey ${args.publicKey}`)
throw new Error(`invalid authentication payload of requesting community with publicKey ${argsPublicKey.asHex()}`)
}
if (!uint32Schema.safeParse(Number(authArgs.oneTimeCode)).success) {
const validOneTimeCode = uint32Schema.safeParse(Number(authArgs.oneTimeCode))
if (!validOneTimeCode.success) {
throw new Error(
`invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${authArgs.publicKey}, expect uint32`
`invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${argsPublicKey.asHex()}, expect uint32`
)
}
state = await findPendingCommunityHandshakeOrFailByOneTimeCode(Number(authArgs.oneTimeCode))
state = await findPendingCommunityHandshakeOrFailByOneTimeCode(validOneTimeCode.data)
const stateLogic = new CommunityHandshakeStateLogic(state)
if (await stateLogic.isTimeoutUpdate() || state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK) {
if (
await stateLogic.isTimeoutUpdate() ||
state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
) {
throw new Error('No valid pending community handshake found')
}
state.status = CommunityHandshakeStateType.SUCCESS
@ -156,16 +156,19 @@ export class AuthenticationResolver {
const authCom = state.federatedCommunity.community
if (authCom) {
methodLogger.debug('found authCom:', new CommunityLoggingView(authCom))
methodLogger.debug('authCom.publicKey', authCom.publicKey.toString('hex'))
methodLogger.debug('args.publicKey', args.publicKey)
if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) {
const authComPublicKey = new Ed25519PublicKey(authCom.publicKey)
methodLogger.debug('authCom.publicKey', authComPublicKey.asHex())
methodLogger.debug('args.publicKey', argsPublicKey.asHex())
if (!authComPublicKey.isSame(argsPublicKey)) {
throw new Error(
`corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}`
`corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${argsPublicKey.asHex()}`
)
}
const communityUuid = uuidv4Schema.safeParse(authArgs.uuid)
if (!communityUuid.success) {
throw new Error(`invalid uuid: ${authArgs.uuid} for community with publicKey ${authCom.publicKey}`)
throw new Error(
`invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}`
)
}
authCom.communityUuid = communityUuid.data
authCom.authenticatedAt = new Date()

View File

@ -11,7 +11,6 @@ import {
getHomeCommunityWithFederatedCommunityOrFail,
} from 'database'
import { getLogger, Logger } from 'log4js'
import { validate as validateUUID, version as versionUUID } from 'uuid'
import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory'
import { randombytes_random } from 'sodium-native'
@ -21,8 +20,10 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import {
AuthenticationJwtPayloadType,
AuthenticationResponseJwtPayloadType,
Ed25519PublicKey,
encryptAndSign,
OpenConnectionCallbackJwtPayloadType,
uuidv4Schema,
verifyAndDecrypt
} from 'shared'
import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType } from 'database'
@ -42,7 +43,7 @@ async function errorState(
export async function startOpenConnectionCallback(
handshakeID: string,
publicKey: string,
publicKey: Ed25519PublicKey,
api: string,
): Promise<void> {
const methodLogger = createLogger('startOpenConnectionCallback')
@ -50,8 +51,7 @@ export async function startOpenConnectionCallback(
methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, {
publicKey,
})
const publicKeyBuffer = Buffer.from(publicKey, 'hex')
const pendingState = await findPendingCommunityHandshake(publicKeyBuffer, api, false)
const pendingState = await findPendingCommunityHandshake(publicKey, api, false)
if (pendingState) {
const stateLogic = new CommunityHandshakeStateLogic(pendingState)
// retry on timeout or failure
@ -66,7 +66,7 @@ export async function startOpenConnectionCallback(
try {
const [homeComB, comA] = await Promise.all([
getHomeCommunityWithFederatedCommunityOrFail(api),
getCommunityWithFederatedCommunityWithApiOrFail(publicKeyBuffer, api),
getCommunityWithFederatedCommunityWithApiOrFail(publicKey, api),
])
// load helpers
const homeComBLogic = new CommunityLogic(homeComB)
@ -79,7 +79,7 @@ export async function startOpenConnectionCallback(
const oneTimeCode = randombytes_random()
const oneTimeCodeString = oneTimeCode.toString()
state.publicKey = publicKeyBuffer
state.publicKey = publicKey.asBuffer()
state.apiVersion = api
state.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
state.handshakeId = parseInt(handshakeID)
@ -100,7 +100,7 @@ export async function startOpenConnectionCallback(
// encrypt callbackArgs with requestedCom.publicJwtKey and sign it with homeCom.privateJwtKey
const jwt = await encryptAndSign(callbackArgs, homeComB.privateJwtKey!, comA.publicJwtKey!)
const args = new EncryptedTransferArgs()
args.publicKey = homeComB.publicKey.toString('hex')
args.publicKey = new Ed25519PublicKey(homeComB.publicKey).asHex()
args.jwt = jwt
args.handshakeID = handshakeID
await stateSaveResolver
@ -140,21 +140,25 @@ export async function startAuthentication(
})
let state: DbCommunityHandshakeState | null = null
let stateSaveResolver: Promise<DbCommunityHandshakeState> | undefined = undefined
const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey)
try {
const homeComA = await getHomeCommunity()
const comB = await DbCommunity.findOneByOrFail({
foreign: true,
publicKey: fedComB.publicKey,
publicKey: fedComBPublicKey.asBuffer(),
})
if (!comB.publicJwtKey) {
throw new Error('Public JWT key still not exist for foreign community')
}
state = await findPendingCommunityHandshake(fedComB.publicKey, fedComB.apiVersion, false)
state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false)
if (!state) {
throw new Error('No pending community handshake found')
}
const stateLogic = new CommunityHandshakeStateLogic(state)
if (await stateLogic.isTimeoutUpdate() || state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION) {
if (
await stateLogic.isTimeoutUpdate() ||
state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
) {
methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state))
throw new Error('No valid pending community handshake found')
}
@ -168,7 +172,7 @@ export async function startAuthentication(
// encrypt authenticationArgs.uuid with fedComB.publicJwtKey and sign it with homeCom.privateJwtKey
const jwt = await encryptAndSign(authenticationArgs, homeComA!.privateJwtKey!, comB.publicJwtKey!)
const args = new EncryptedTransferArgs()
args.publicKey = homeComA!.publicKey.toString('hex')
args.publicKey = new Ed25519PublicKey(homeComA!.publicKey).asHex()
args.jwt = jwt
args.handshakeID = handshakeID
methodLogger.debug(`invoke authenticate() with:`, args)
@ -183,10 +187,10 @@ export async function startAuthentication(
new FederatedCommunityLoggingView(fedComB),
)
if (payload.tokentype !== AuthenticationResponseJwtPayloadType.AUTHENTICATION_RESPONSE_TYPE) {
throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${comB.publicKey}`)
throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`)
}
if (!payload.uuid || !validateUUID(payload.uuid) || versionUUID(payload.uuid) !== 4) {
throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${comB.publicKey}`)
if (!uuidv4Schema.safeParse(payload.uuid).success) {
throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`)
}
comB.communityUuid = payload.uuid
comB.authenticatedAt = new Date()

View File

@ -0,0 +1,41 @@
/**
* Class mainly for handling ed25519 public keys,
* to make sure we have always the correct Format (Buffer or Hex String)
*/
export class BinaryData {
private buf: Buffer
private hex: string
constructor(input: Buffer | string | undefined) {
if (typeof input === 'string') {
this.buf = Buffer.from(input, 'hex')
this.hex = input
} else if (Buffer.isBuffer(input)) {
this.buf = input
this.hex = input.toString('hex')
} else {
throw new Error('Either valid hex string or Buffer expected')
}
}
asBuffer(): Buffer {
return this.buf
}
asHex(): string {
return this.hex
}
isSame(other: BinaryData): boolean {
return this.buf.compare(other.buf) === 0
}
}
export class Ed25519PublicKey extends BinaryData {
constructor(input: Buffer | string | undefined) {
super(input)
if (this.asBuffer().length !== 32) {
throw new Error('Invalid ed25519 public key length')
}
}
}

View File

@ -1 +1,2 @@
export * from './updateField'
export * from './updateField'
export * from './BinaryData'

View File

@ -1,5 +1,6 @@
import { describe, expect, it } from 'bun:test'
import { uuidv4Schema, uint32Schema } from './base.schema'
import { generateKeyPairSync } from 'node:crypto'
import { uuidv4Schema, uint32Schema, buffer32Schema } from './base.schema'
import { v4 as uuidv4 } from 'uuid'
describe('uuidv4 schema', () => {
@ -22,3 +23,17 @@ describe('uint32 schema', () => {
expect(uint32Schema.safeParse(2092352810).success).toBeTruthy()
})
})
describe('buffer32 schema', () => {
it('should validate buffer', () => {
const { publicKey } = generateKeyPairSync('ed25519')
const buffer = publicKey.export({ type: 'spki', format: 'der' }).slice(-32)
expect(Buffer.isBuffer(buffer)).toBeTruthy()
expect(buffer.length).toBe(32)
expect(buffer32Schema.safeParse(buffer).success).toBeTruthy()
})
it("shouldn't validate string", () => {
expect(buffer32Schema.safeParse('3e1a2eecc95c48fedf47a522a8c77b91').success).toBeFalsy()
})
})

View File

@ -1,7 +1,12 @@
import { string, number } from 'zod'
import { string, number, custom } from 'zod'
import { validate, version } from 'uuid'
export const uuidv4Schema = string().refine((val: string) => validate(val) && version(val) === 4, 'Invalid uuid')
export const emailSchema = string().email()
export const urlSchema = string().url()
export const uint32Schema = number().positive().lte(4294967295)
export const uint32Schema = number().positive().lte(4294967295)
export const buffer32Schema = custom<Buffer>(
(val: Buffer) => Buffer.isBuffer(val) && val.length === 32,
'Invalid buffer'
)
export const hex64Schema = string().length(64).regex(/^[0-9A-Fa-f]$/)