From 0dc18c4937362aebb9ff9d03cdd244c35581c0f9 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei Date: Mon, 26 Aug 2024 20:40:11 +0400 Subject: [PATCH 1/4] feat: add decrypt-attestation-secret api --- src/eas/dto/decrypt-attestation-secret.dto.ts | 25 +++++++++++++ src/eas/eas.controller.ts | 36 +++++++++++++++++-- src/eas/eas.service.ts | 13 +++++-- src/lit/lit.service.ts | 10 ++---- 4 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 src/eas/dto/decrypt-attestation-secret.dto.ts diff --git a/src/eas/dto/decrypt-attestation-secret.dto.ts b/src/eas/dto/decrypt-attestation-secret.dto.ts new file mode 100644 index 0000000..7c95aae --- /dev/null +++ b/src/eas/dto/decrypt-attestation-secret.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsString, IsNotEmpty, IsNumber } from 'class-validator' +import { JwtProvider } from '../../shared/decorators/jwt-provider.decorator' +import { AUTH_PROVIDERS } from '../../auth/constants/provider.constants' + +export class DecryptAttestationSecretDto { + @ApiProperty({ + description: 'The siwe JWT', + example: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6', + required: true, + }) + @IsString() + @IsNotEmpty() + @JwtProvider(AUTH_PROVIDERS.GOOGLE) + readonly siweJwt: string + @ApiProperty({ + description: 'Chain Id', + example: '11155111', + required: true, + }) + @IsNumber() + @IsNotEmpty() + readonly chainId: number +} diff --git a/src/eas/eas.controller.ts b/src/eas/eas.controller.ts index be6485a..3bf6b33 100644 --- a/src/eas/eas.controller.ts +++ b/src/eas/eas.controller.ts @@ -10,6 +10,8 @@ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger' import { AuthService } from '../auth/auth.service' import { SignDelegatedAttestationDto } from './dto/sign-delegated-attestation.dto' import { SignDelegatedRevocationDto } from './dto/sign-delegated-revocation.dto' +import { DecryptAttestationSecretDto } from './dto/decrypt-attestation-secret.dto' + import { EasService } from '../eas/eas.service' import { LitService } from '../lit/lit.service' import { keccak256, toHex } from 'viem' @@ -66,7 +68,7 @@ export class EasController { }) @ApiResponse({ status: 200, - description: 'Get signed delegated revocation via attestation uid', + description: 'Get signed delegated revocation by attestation uid', }) @ApiParam({ name: 'uid', type: 'string', description: 'attestation uid' }) @HttpCode(HttpStatus.OK) @@ -77,7 +79,8 @@ export class EasController { const { chainId, siweJwt } = signDelegatedRevocationDto const siweJwtPayload = await this.authService.validateToken(siweJwt) const attestation = await this.easService.getAttestaion(chainId, uid) - await this.easService.revokeable( + await this.easService.checkAttestar(attestation) + await this.easService.checkRecipient( attestation, siweJwtPayload.sub as '0x${string}' ) @@ -85,4 +88,33 @@ export class EasController { await this.easService.getSignedDelegatedRevocation(chainId, uid) ) } + + @Post(':uid/decrypt-attestation-secret') + @ApiOperation({ + summary: 'decrypt attestation secret ', + }) + @ApiResponse({ + status: 200, + description: 'decrypt attestation secret via attestation uid', + }) + @ApiParam({ name: 'uid', type: 'string', description: 'attestation uid' }) + @HttpCode(HttpStatus.OK) + async decryptSecret( + @Param('uid') uid: string, + @Body() decryptAttestationSecretDto: DecryptAttestationSecretDto + ) { + const { chainId, siweJwt } = decryptAttestationSecretDto + const siweJwtPayload = await this.authService.validateToken(siweJwt) + const attestation = await this.easService.getAttestaion(chainId, uid) + await this.easService.checkAttestar(attestation) + await this.easService.checkRecipient( + attestation, + siweJwtPayload.sub as '0x${string}' + ) + const decodedData = this.easService.decodettestationData( + attestation.data + ) + const secret = decodedData[2].value.value + return await this.litService.decryptFromJson(chainId, secret) + } } diff --git a/src/eas/eas.service.ts b/src/eas/eas.service.ts index 325b0a8..e696948 100644 --- a/src/eas/eas.service.ts +++ b/src/eas/eas.service.ts @@ -19,6 +19,7 @@ import { NO_EXPIRATION, ZERO_BYTES32, Attestation, + SchemaDecodedItem, } from '@ethereum-attestation-service/eas-sdk' import { Address } from 'viem' import { PinoLogger, InjectPinoLogger } from 'nestjs-pino' @@ -70,8 +71,7 @@ export class EasService { getAttester(chainId: SupportedChainId): Signer { return this.attesters.get(chainId) } - - private encodeAttestationData(params: any[]): string { + encodeAttestationData(params: any[]): string { const schemaEncoder = new SchemaEncoder(SCHEMA_TYPES) return schemaEncoder.encodeData([ { name: 'key', value: params[0], type: 'bytes32' }, @@ -80,6 +80,11 @@ export class EasService { ]) } + decodettestationData(data: string): SchemaDecodedItem[] { + const schemaEncoder = new SchemaEncoder(SCHEMA_TYPES) + return schemaEncoder.decodeData(data) + } + private buildAttestationPayload( chainId: SupportedChainId, encodedData: string, @@ -97,7 +102,7 @@ export class EasService { } } - revokeable(attestation: Attestation, recipient: Address) { + checkAttestar(attestation: Attestation) { const privateKey = this.configService.get( 'wallet.privateKey' ) as '0x${string}' @@ -106,6 +111,8 @@ export class EasService { `We aren't attester of this attesation` ) } + } + checkRecipient(attestation: Attestation, recipient: Address) { if (attestation.recipient !== recipient) { throw new ForbiddenException( `You aren't recipient of this attesation` diff --git a/src/lit/lit.service.ts b/src/lit/lit.service.ts index 62a78ad..5f94cfe 100644 --- a/src/lit/lit.service.ts +++ b/src/lit/lit.service.ts @@ -1,8 +1,4 @@ -import { - Injectable, - BadRequestException, - InternalServerErrorException, -} from '@nestjs/common' +import { Injectable, InternalServerErrorException } from '@nestjs/common' import { LitNodeClientNodeJs, encryptToJson, @@ -148,7 +144,7 @@ export class LitService { }, chain: this.chainIdToLitChainName(chainId), returnValueTest: { - key: '', + key: 'isMember', comparator: '=', value: 'true', }, @@ -237,10 +233,10 @@ export class LitService { } async decryptFromJson(chainId: SupportedChainId, dataToDecrypt: any) { - const sessionSigs = await this.getSessionSigsViaAuthSig(chainId) if (!this.litNodeClient) { await this.connect() } + const sessionSigs = await this.getSessionSigsViaAuthSig(chainId) try { return await decryptFromJson({ litNodeClient: this.litNodeClient, From cdd526c4d2e24b7eda39cca5dd4d6f465a7bbb5b Mon Sep 17 00:00:00 2001 From: Behzad Rabiei Date: Mon, 26 Aug 2024 20:44:19 +0400 Subject: [PATCH 2/4] fix: fix the issues of sign-delegated-revocation params --- src/eas/eas.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eas/eas.controller.ts b/src/eas/eas.controller.ts index 3bf6b33..f743395 100644 --- a/src/eas/eas.controller.ts +++ b/src/eas/eas.controller.ts @@ -62,7 +62,7 @@ export class EasController { ) } - @Post(':id/sign-delegated-revocation') + @Post(':uid/sign-delegated-revocation') @ApiOperation({ summary: 'get signed delegated revocation ', }) @@ -73,7 +73,7 @@ export class EasController { @ApiParam({ name: 'uid', type: 'string', description: 'attestation uid' }) @HttpCode(HttpStatus.OK) async revoke( - @Param() uid: string, + @Param('uid') uid: string, @Body() signDelegatedRevocationDto: SignDelegatedRevocationDto ) { const { chainId, siweJwt } = signDelegatedRevocationDto From d1c4c759bd4edf9a68150ec3c8a06e67b92d2e73 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei Date: Mon, 26 Aug 2024 20:49:49 +0400 Subject: [PATCH 3/4] fix: fix the type of validate token error --- src/auth/auth.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index a4961e4..2a2d1c1 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable, BadRequestException } from '@nestjs/common' +import { Injectable, UnauthorizedException } from '@nestjs/common' import * as jwt from 'jsonwebtoken' import { JwtPayload } from './types/jwt-payload.type' import { ConfigService } from '@nestjs/config' @@ -30,7 +30,7 @@ export class AuthService { ) as JwtPayload } catch (error) { this.logger.error(error, `Failed to validtae token`) - throw new BadRequestException(error.message) + throw new UnauthorizedException(error.message) } } From 4ba7adb7a9580d5e1cc4d3ed956ecf4d9e01b253 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei Date: Mon, 26 Aug 2024 20:56:30 +0400 Subject: [PATCH 4/4] test: update tests --- src/auth/auth.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index a770e5d..264f08f 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -7,7 +7,7 @@ import * as moment from 'moment' import { JwtPayload } from './types/jwt-payload.type' import * as jwt from 'jsonwebtoken' import { PinoLogger, LoggerModule } from 'nestjs-pino' -import { BadRequestException } from '@nestjs/common' +import { UnauthorizedException } from '@nestjs/common' const mockPublicKey = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' @@ -82,7 +82,7 @@ describe('AuthService', () => { it('should return null if token is invalid', async () => { await expect( service.validateToken('invalid.token.here') - ).rejects.toThrow(BadRequestException) + ).rejects.toThrow(UnauthorizedException) }) }) })