From a3062518f6e488e1baf90acbce0b0a92dd751907 Mon Sep 17 00:00:00 2001 From: Godfr3y Date: Tue, 16 Jun 2026 12:40:48 +0100 Subject: [PATCH 1/2] feat: add SEP-0043 signature verification for browser wallets Added SEP-0043 fallback for authentication: the service tries raw Ed25519 first, then verifies the SEP-0043-prefixed message "Stellar Signing Key: " if raw verification fails. Closed #19 --- src/modules/auth/auth.service.ts | 21 ++++++++++++++++++++- src/modules/auth/dto/verify-request.dto.ts | 12 +++++++++++- test/unit/modules/auth/auth.service.spec.ts | 15 +++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 0cc77a0..2f7cbc6 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -100,7 +100,26 @@ export class AuthService { } try { const keypair = Keypair.fromPublicKey(dto.wallet); - const isValid = keypair.verify(Buffer.from(dto.nonce), Buffer.from(dto.signature, 'base64')); + + let isValid = false; + + // First attempt: raw Ed25519 signature (mobile clients) + try { + isValid = keypair.verify(Buffer.from(dto.nonce), Buffer.from(dto.signature, 'base64')); + } catch (e) { + isValid = false; + } + + // If raw verification failed, try SEP-0043 (browser wallets like Freighter) + if (!isValid) { + try { + const sepMessage = 'Stellar Signing Key: ' + dto.nonce; + isValid = keypair.verify(Buffer.from(sepMessage), Buffer.from(dto.signature, 'base64')); + } catch (e) { + isValid = false; + } + } + if (!isValid) { throw new UnauthorizedException({ code: 'AUTH_SIGNATURE_INVALID', message: 'Invalid signature.' }); } diff --git a/src/modules/auth/dto/verify-request.dto.ts b/src/modules/auth/dto/verify-request.dto.ts index 6f1ccfe..18160c7 100644 --- a/src/modules/auth/dto/verify-request.dto.ts +++ b/src/modules/auth/dto/verify-request.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsNotEmpty, Matches, Length } from 'class-validator'; +import { IsString, IsNotEmpty, Matches, Length, IsOptional, IsIn } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; /** @@ -44,3 +44,13 @@ export class VerifyRequestDto { @IsNotEmpty({ message: 'Signature is required' }) signature: string; } + @ApiProperty({ + description: "Signature type — 'raw' for raw Ed25519 or 'sep0043' for browser wallets", + example: 'raw', + required: false, + enum: ['raw', 'sep0043'], + }) + @IsOptional() + @IsString() + @IsIn(['raw', 'sep0043']) + signatureType?: 'raw' | 'sep0043' = 'raw'; diff --git a/test/unit/modules/auth/auth.service.spec.ts b/test/unit/modules/auth/auth.service.spec.ts index 7d89525..71ffc37 100644 --- a/test/unit/modules/auth/auth.service.spec.ts +++ b/test/unit/modules/auth/auth.service.spec.ts @@ -243,6 +243,21 @@ describe('AuthService', () => { ); }); + it('should verify using SEP-0043 if raw verification fails', async () => { + const { mockKeypair } = setupMocks({ signatureValid: false }); + // First call (raw) returns false, second call (sep0043) should return true + mockKeypair.verify.mockImplementationOnce(() => false).mockImplementationOnce(() => true); + + await expect(service.verifySignature(validDto)).resolves.toBeUndefined(); + + expect(Keypair.fromPublicKey).toHaveBeenCalledWith(validWallet); + expect(mockKeypair.verify).toHaveBeenCalledWith(Buffer.from(validNonce), Buffer.from(validSignature, 'base64')); + expect(mockKeypair.verify).toHaveBeenCalledWith( + Buffer.from('Stellar Signing Key: ' + validNonce), + Buffer.from(validSignature, 'base64'), + ); + }); + it('should mark nonce as used after successful verification', async () => { const { } = setupMocks(); await service.verifySignature(validDto); From b2852088ccf5f358c1bb139988f54d11f55c037c Mon Sep 17 00:00:00 2001 From: Godfr3y Date: Tue, 16 Jun 2026 13:38:34 +0100 Subject: [PATCH 2/2] fix: correct signatureType property declaration in VerifyRequestDto --- src/modules/auth/dto/verify-request.dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/auth/dto/verify-request.dto.ts b/src/modules/auth/dto/verify-request.dto.ts index 18160c7..1057b1b 100644 --- a/src/modules/auth/dto/verify-request.dto.ts +++ b/src/modules/auth/dto/verify-request.dto.ts @@ -43,7 +43,7 @@ export class VerifyRequestDto { @IsString() @IsNotEmpty({ message: 'Signature is required' }) signature: string; -} + @ApiProperty({ description: "Signature type — 'raw' for raw Ed25519 or 'sep0043' for browser wallets", example: 'raw', @@ -54,3 +54,4 @@ export class VerifyRequestDto { @IsString() @IsIn(['raw', 'sep0043']) signatureType?: 'raw' | 'sep0043' = 'raw'; +}