dfinity / agent-js

A collection of libraries and tools for building software around the Internet Computer, in JavaScript.
https://agent-js.icp.xyz
Apache License 2.0
155 stars 96 forks source link

Ed25519KeyIdentity.verify incorrect implementation #943

Open adpopescu338 opened 1 month ago

adpopescu338 commented 1 month ago

Ed25519KeyIdentity.verify is calling ed25519.verify with the wrong args

To Reproduce

import { Ed25519KeyIdentity } from '@dfinity/identity';

// Arguments
const args = [
  'vfKdMCDuw5sx85POiPwPNJ+OMp9711ORElAET0G1KpqoausXE4QUVjqalfQ7z+ZKlW/MWtHwlqHLRJVwPrR6Cg==',
  '{"termsAndConditionsId":"default","termsAndConditionsConsent":true,"emailAddress":"ad.popescu338@yahoo.com","temporaryIdentityPublicKeyDerBase64":"MCowBQYDK2VwAyEAQgA3YzGamtPWxowSoUrwtew+lha45zKXay+IycoZOwA="}',
  'MCowBQYDK2VwAyEAQgA3YzGamtPWxowSoUrwtew+lha45zKXay+IycoZOwA=',
];

// Decode the signature from base64
const signature = Uint8Array.from(Buffer.from(args[0], 'base64')); // Convert to Uint8Array
console.log('Decoded signature length:', signature.length); // Check the length of the signature

// Decode the body (JSON string)
const message = Uint8Array.from(Buffer.from(args[1], 'utf8')); // Convert to Uint8Array
console.log('Decoded message length:', message.length); // Check the length of the message

// Decode the public key from base64
const temporaryIdentityPublicKeyDerBase64 = Uint8Array.from(
  Buffer.from(args[2], 'base64')
); // Convert to Uint8Array

console.log(
  'Decoded public key length:',
  temporaryIdentityPublicKeyDerBase64.length
); // Check the length of the public key

const result = Ed25519KeyIdentity.verify(
  signature,
  message,
  temporaryIdentityPublicKeyDerBase64
);

console.log('Verification result:', result);

Output:

Decoded signature length: 64
Decoded message length: 209
Decoded public key length: 44
/test/node_modules/@noble/curves/abstract/utils.js:152
        throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
              ^

Error: signature expected 64 bytes, got 209
    at ensureBytes (/test/node_modules/@noble/curves/abstract/utils.js:152:15)
    at Object.verify (/test/node_modules/@noble/curves/abstract/edwards.js:386:42)
    at Ed25519KeyIdentity.verify (/test/node_modules/@dfinity/identity/lib/cjs/identity/ed25519.js:211:34)
    at file:///test/src/test.mjs:28:35
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)

Node.js v20.11.0

If we swap message and signature as follows, it doesn't error out anymore, but the output will be false

const result = Ed25519KeyIdentity.verify(
    message,
  signature,
  temporaryIdentityPublicKeyDerBase64
);

Output:

Decoded signature length: 64
Decoded message length: 209
Decoded public key length: 44
Verification result: false

Screenshots

Screenshot 2024-10-16 at 08 08 53

The problem most likely is caused by THIS LINE, where we call ed25519.verify(message, signature, publicKey), but ed25519.verify expects the signature as first arg and message as second.

/**
 * Edwards Curve interface.
 * Main methods: `getPublicKey(priv)`, `sign(msg, priv)`, `verify(sig, msg, pub)`.
 */
export type CurveFn = {
  CURVE: ReturnType<typeof validateOpts>;
  getPublicKey: (privateKey: Hex) => Uint8Array;
  sign: (message: Hex, privateKey: Hex, options?: { context?: Hex }) => Uint8Array;
  verify: (
    sig: Hex,
    message: Hex,
    publicKey: Hex,
    options?: { context?: Hex; zip215: boolean }
  ) => boolean;
  ExtendedPoint: ExtPointConstructor;
  utils: {
    randomPrivateKey: () => Uint8Array;
    getExtendedPublicKey: (key: Hex) => {
      head: Uint8Array;
      prefix: Uint8Array;
      scalar: bigint;
      point: ExtPointType;
      pointBytes: Uint8Array;
    };
  };
};