spruceid / siwe

Sign-In with Ethereum library
https://login.xyz
Apache License 2.0
1.05k stars 143 forks source link

I'm not getting the right address when checking #186

Closed starker-xp closed 10 months ago

starker-xp commented 10 months ago

I'm trying to integrate Siwe into my signature system. I want to be able to do a server signature using siwe even if it's not very useful. But now I've been stalling for a week, what haven't I understood?

Thanks in advance Here is my code :

import { Logger } from '@nestjs/common';
import Web3 from 'web3';
import { RegisteredSubscription } from 'web3/lib/commonjs/eth.exports';
export interface ISignature<T> {
  createSignature(message: T): string;
  recoverSignature(signature: string, message: T): Promise<string>;
}

export abstract class BaseSignatureService<T> implements ISignature<T> {

  constructor(
    protected readonly web3: Web3<RegisteredSubscription>,
    protected readonly privateKey: string,
  ) {}
  abstract createSignature(message: T): string;
  abstract recoverSignature(signature: string, message: T): Promise<string>;
}
import { Injectable } from '@nestjs/common';
import { BaseSignatureService } from './base-signature.service';
import { SiweMessage } from 'siwe';
import { utf8ToHex } from 'web3-utils';
@Injectable()
export class SiweService extends BaseSignatureService<SiweMessage> {
  public createSignature(message: SiweMessage): string {
    const signature = this.web3.eth.accounts.sign(SiweService.encodeDefunct(message.prepareMessage()), this.privateKey);
    return signature.signature;
  }

  static encodeDefunct(primitive) {
    const messageBytes = utf8ToHex(primitive);

    const msgLength = Buffer.from(messageBytes.slice(2), 'hex').length.toString();
    return '\x19Ethereum Signed Message:\n' + msgLength + messageBytes;
  }

  public async recoverSignature(signature: string, message: SiweMessage): Promise<string> {
    const { data } = await message.verify({
      signature,
    });
    return this.web3.utils.toChecksumAddress(data.address);
  }
}
import { Test, TestingModule } from '@nestjs/testing';
import Web3 from 'web3';

import { SiweMessage } from 'siwe';
import { SiweService } from './siwe.service';

const privateKey = '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728';
const publicKey = '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0';
const blockchainAddress = 'https://mainnet-mainnet.infura.io/v3/<myKey>';

const createSignatureInTest = (message: SiweMessage) => {
  const web3 = new Web3(new Web3.providers.HttpProvider(blockchainAddress));
  const signature = web3.eth.accounts.sign(SiweService.encodeDefunct(message.prepareMessage()), privateKey);
  return signature.signature;
};

describe('SiweService', () => {
  let service: SiweService;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [],
      providers: [
        {
          provide: SiweService,
          useFactory: () => {
            const web3 = new Web3(new Web3.providers.HttpProvider(blockchainAddress));
            return new SiweService(web3, privateKey);
          },
        },
      ],
    }).compile();

    service = module.get<SiweService>(SiweService);
  });

  beforeEach(async () => {
    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should use createSignature method with object', () => {
    const wallet = '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0';
    const signatureMessage = createSignatureInTest(
      new SiweMessage({
        domain: 'localhost:3000',
        address: wallet,
        statement: "I don't understand",
        uri: 'http://localhost:3000',
        version: '1',
        chainId: 1,
        nonce: '0123456789',
        issuedAt: '2023-12-11T08:15:07.083Z',
      }),
    );

    const result = service.createSignature(
      new SiweMessage({
        domain: 'localhost:3000',
        address: wallet,
        statement: "I don't understand",
        uri: 'http://localhost:3000',
        version: '1',
        chainId: 1,
        nonce: '0123456789',
        issuedAt: '2023-12-11T08:15:07.083Z',
      }),
    );
    console.log(signatureMessage);
    expect(result).toEqual(signatureMessage);
  });

  it('should use recoverSignature method with object', async () => {
    const signature = '0xa595e30091a213b488ac4b3549d1aed52ce1f7f622cdf94b30bad6a2fdfd58a27480381023892ef9504b42348f40c272125f93da5ac1795103b0d9605bceadb11b';
    const wallet = '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0';
    const result = await service.recoverSignature(
      signature,
      new SiweMessage({
        domain: 'localhost:3000',
        address: wallet,
        statement: "I don't understand",
        uri: 'http://localhost:3000',
        version: '1',
        chainId: 1,
        nonce: '0123456789',
        issuedAt: '2023-12-11T08:15:07.083Z',
      }),
    );

    expect(result).toBe(publicKey);
  });
});

My unit test give me : ● SiweService › should use recoverSignature method with object

thrown: Object {
  "data": SiweMessage {
    "address": "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0",
    "chainId": 1,
    "domain": "localhost:3000",
    "issuedAt": "2023-12-11T08:15:07.083Z",
    "nonce": "0123456789",
    "statement": "I don't understand",
    "uri": "http://localhost:3000",
    "version": "1",
  },
  "error": SiweError {
    "expected": "0x6eBda42e2C5512182E4033141009C030e6fF7195",
    "received": "Resolved address to be 0xEB014f8c8B418Db6b45774c326A0E64C78914dC0",
    "type": "Signature does not match address of the message.",
  },
  "success": false,
}
sbihel commented 10 months ago

It seems like the error has expected and received mixed up, but your issue is that the signature is incorrect. Are you sure the signature hardcoded in should use recoverSignature method with object is the one returned by should use createSignature method with object?

starker-xp commented 10 months ago

I've just edited the signature hardcoded with the correct value. Sorry

sbihel commented 10 months ago

The sign function already encodes the message into the EIP-151 format https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#sign. If you remove your encodeDefunct function it will work.