Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.' });
}
Expand Down
13 changes: 12 additions & 1 deletion src/modules/auth/dto/verify-request.dto.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand Down Expand Up @@ -43,4 +43,15 @@ 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',
required: false,
enum: ['raw', 'sep0043'],
})
@IsOptional()
@IsString()
@IsIn(['raw', 'sep0043'])
signatureType?: 'raw' | 'sep0043' = 'raw';
}
15 changes: 15 additions & 0 deletions test/unit/modules/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading