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
26 changes: 26 additions & 0 deletions packages/sdk/src/mintlayer-connect-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import initWasm, {
SignatureHashType,
encode_output_htlc,
extract_htlc_secret,
verify_challenge,
} from '@mintlayer/wasm-lib';

const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
Expand Down Expand Up @@ -877,6 +878,12 @@ export type SignChallengeResponse = {
signature: string;
};

export type VerifyChallengeArgs = {
message: string;
address: string;
signature: string;
};

class Client {
private network: 'mainnet' | 'testnet';
private connectedAddresses: {
Expand Down Expand Up @@ -3605,6 +3612,25 @@ class Client {
});
}

/**
* Verifies a signed challenge message.
* Used to verify that a signature was produced by the private key corresponding to the given address.
*
* Note: The provided address must be a 'pubkeyhash' address.
*
* @param args - Object containing message, address, and signature
* @returns Promise that resolves to true if the signature is valid, throws an error otherwise
*/
async verifyChallenge(args: VerifyChallengeArgs): Promise<boolean> {
this.ensureInitialized();

const messageBytes = stringToUint8Array(args.message);
const signatureBytes = hexToUint8Array(args.signature);
const network = this.getMLNetwork();

return verify_challenge(args.address, network, signatureBytes, messageBytes);
}

/**
* Requests a secret hash from the wallet for HTLC operations.
* @param args - Additional arguments (currently unused)
Expand Down
75 changes: 75 additions & 0 deletions packages/sdk/tests/verify-challenge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Client } from '../src/mintlayer-connect-sdk';
import fetchMock from 'jest-fetch-mock';

beforeEach(() => {
fetchMock.resetMocks();

(window as any).mojito = {
isExtension: true,
connect: jest.fn().mockResolvedValue({
addressesByChain: {
mintlayer: {
receiving: ['tmt1q9cz2dkuqqrdv2g3kl8zqvjvqtu3u6j3j8z9z9z9'],
change: ['tmt1q9cz2dkuqqrdv2g3kl8zqvjvqtu3u6j3j8z9z9z8'],
},
},
}),
restore: jest.fn().mockResolvedValue({
testnet: {
receiving: ['tmt1q9cz2dkuqqrdv2g3kl8zqvjvqtu3u6j3j8z9z9z9'],
},
}),
disconnect: jest.fn().mockResolvedValue(undefined),
request: jest.fn().mockResolvedValue('signed-transaction'),
};

fetchMock.doMock();

fetchMock.mockResponse(async req => {
const url = req.url;

if (url.endsWith('/chain/tip')) {
return JSON.stringify({ height: 200000 });
}

console.warn('No mock for:', url);
return JSON.stringify({ error: 'No mock defined' });
});
});

test('verifyChallenge should verify a valid signature', async () => {
const client = await Client.create({
network: 'testnet',
autoRestore: false,
});

await client.connect();

// This is a test case - in real usage, the signature would come from signChallenge
// For now, we're testing that the method exists and can be called
const testMessage = 'Hello, Mintlayer!';
const testAddress = 'tmt1q9cz2dkuqqrdv2g3kl8zqvjvqtu3u6j3j8z9z9z9';
const testSignature = '0123456789abcdef'; // This would be a real signature in practice

// The actual verification will fail with invalid signature, but we're testing the method exists
try {
await client.verifyChallenge({
message: testMessage,
address: testAddress,
signature: testSignature,
});
} catch (error) {
// Expected to fail with invalid signature, but the method should exist and be callable
expect(error).toBeDefined();
}
});

test('verifyChallenge should be a function on Client', async () => {
const client = await Client.create({
network: 'testnet',
autoRestore: false,
});

expect(typeof client.verifyChallenge).toBe('function');
});

Loading