Skip to content

Commit

Permalink
Merge pull request near#1309 from LimeChain/biometric-ed25519-bug-fix
Browse files Browse the repository at this point in the history
Biometric-ed25519 package bug fix
  • Loading branch information
gtsonevv authored Mar 7, 2024
2 parents a12f235 + e7e6d83 commit 265b497
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-plants-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@near-js/biometric-ed25519": patch
---

Fix recoverPublicKey(), createKey() and getKeys() methods.
5 changes: 2 additions & 3 deletions packages/biometric-ed25519/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@
"author": "Pagoda",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "4.0.0",
"@hexagon/base64": "1.1.26",
"@near-js/crypto": "workspace:*",
"@near-js/utils": "workspace:*",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.3",
"asn1-parser": "1.1.8",
"bn.js": "5.2.1",
"borsh": "1.0.0",
"buffer": "6.0.3",
"@noble/curves": "1.2.0",
"fido2-lib": "3.4.1"
},
"devDependencies": {
"@aws-sdk/types": "3.329.0",
"@types/node": "18.11.18"
}
}
36 changes: 15 additions & 21 deletions packages/biometric-ed25519/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import base64 from '@hexagon/base64';
import { ed25519 } from '@noble/curves/ed25519';
import { Sha256 } from '@aws-crypto/sha256-js';
import { sha256 } from '@noble/hashes/sha256';
import { Buffer } from 'buffer';
import asn1 from 'asn1-parser';
import { KeyPair } from '@near-js/crypto';
Expand All @@ -11,7 +11,8 @@ import {
get64BytePublicKeyFromPEM,
preformatGetAssertReq,
publicKeyCredentialToJSON,
recoverPublicKey
recoverPublicKey,
uint8ArrayToBigInt
} from './utils';
import { Fido2 } from './fido2';
import { AssertionResponse } from './index.d';
Expand Down Expand Up @@ -69,11 +70,9 @@ export const createKey = async (username: string): Promise<KeyPair> => {
});
const publicKey = result.authnrData.get('credentialPublicKeyPem');
const publicKeyBytes = get64BytePublicKeyFromPEM(publicKey);
const edSha256 = new Sha256();
edSha256.update(Buffer.from(publicKeyBytes));
const secretKey = await edSha256.digest();
const secretKey = sha256.create().update(Buffer.from(publicKeyBytes)).digest();
const pubKey = ed25519.getPublicKey(secretKey);
return KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([secretKey, Buffer.from(pubKey)]))));
return KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([Buffer.from(secretKey), Buffer.from(pubKey)]))));
});
};

Expand Down Expand Up @@ -101,27 +100,22 @@ export const getKeys = async (username: string): Promise<[KeyPair, KeyPair]> =>
//@ts-ignore
const parser = asn1?.ASN1?.parse || window?.ASN1?.parse;
const rAndS = parser(new Uint8Array(signature));
const clientDataSha256 = new Sha256();
clientDataSha256.update(
const clientDataJSONHash = sha256.create().update(
Buffer.from(new Uint8Array(base64.toArrayBuffer(getAssertionResponse.response.clientDataJSON, true)))
);
const clientDataJSONHash = await clientDataSha256.digest();
).digest();
const authenticatorDataJSONHash = Buffer.from(new Uint8Array(base64.toArrayBuffer(getAssertionResponse.response.authenticatorData, true)));
const authenticatorAndClientDataJSONHash = Buffer.concat([authenticatorDataJSONHash, clientDataJSONHash]);
const authenticatorAndClientDataJSONHash = Buffer.concat([Buffer.from(authenticatorDataJSONHash), Buffer.from(clientDataJSONHash)]);

const correctPKs = await recoverPublicKey(rAndS.children[0].value, rAndS.children[1].value, authenticatorAndClientDataJSONHash, 0);

const firstEdSha256 = new Sha256();
firstEdSha256.update(Buffer.from(correctPKs[0]));
const secondEdSha256 = new Sha256();
secondEdSha256.update(Buffer.from(correctPKs[1]));
const r = rAndS.children[0].value;
const s = rAndS.children[1].value;
const correctPKs = await recoverPublicKey(uint8ArrayToBigInt(r), uint8ArrayToBigInt(s), authenticatorAndClientDataJSONHash, 0);

const firstEDSecret = await firstEdSha256.digest();
const firstEDSecret = sha256.create().update(Buffer.from(correctPKs[0])).digest();
const firstEDPublic = ed25519.getPublicKey(firstEDSecret);
const secondEDSecret = await secondEdSha256.digest();
const secondEDSecret = sha256.create().update(Buffer.from(correctPKs[1])).digest();
const secondEDPublic = ed25519.getPublicKey(secondEDSecret);
const firstKeyPair = KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([firstEDSecret, Buffer.from(firstEDPublic)]))));
const secondKeyPair = KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([secondEDSecret, Buffer.from(secondEDPublic)]))));
const firstKeyPair = KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([Buffer.from(firstEDSecret), Buffer.from(firstEDPublic)]))));
const secondKeyPair = KeyPair.fromString(baseEncode(new Uint8Array(Buffer.concat([Buffer.from(secondEDSecret), Buffer.from(secondEDPublic)]))));
return [firstKeyPair, secondKeyPair];
});
};
Expand Down
33 changes: 14 additions & 19 deletions packages/biometric-ed25519/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import base64 from '@hexagon/base64';
import { secp256k1 } from '@noble/curves/secp256k1';
import { Sha256 } from '@aws-crypto/sha256-js';
import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256';
import { PublicKey } from '@near-js/crypto';

export const preformatMakeCredReq = (makeCredReq) => {
Expand All @@ -25,7 +25,7 @@ export const preformatMakeCredReq = (makeCredReq) => {
export const get64BytePublicKeyFromPEM = (publicKey: PublicKey) => {
const prefix = '\n';
const publicKeyBase64 = publicKey.toString().split(prefix);
return base64.toArrayBuffer(`${publicKeyBase64[1]}${publicKeyBase64[2]}`).slice(27);
return base64.toArrayBuffer(`${publicKeyBase64[1]}${publicKeyBase64[2]}`).slice(27, 59);
};

export const validateUsername = (name: string): string => {
Expand Down Expand Up @@ -77,22 +77,17 @@ export const recoverPublicKey = async (r, s, message, recovery) => {
if (recovery !== 0 && recovery !== 1) {
throw new Error('Invalid recovery parameter');
}

const hash = new Sha256();
hash.update(message);

const sigObjQ = new secp256k1.Signature(r, s);
sigObjQ.addRecoveryBit(0);
const sigObjP = new secp256k1.Signature(r, s);
sigObjP.addRecoveryBit(1);
const sigObjQ = new p256.Signature(r, s).addRecoveryBit(0);
const sigObjP = new p256.Signature(r, s).addRecoveryBit(1);
const hash = sha256.create().update(message).digest();

const h = await hash.digest();

const Q = sigObjQ.recoverPublicKey(h);
const P = sigObjP.recoverPublicKey(h);

return [
Buffer.from(Q.toRawBytes()).subarray(1, 65),
Buffer.from(P.toRawBytes()).subarray(1, 65)
];
const Q = sigObjQ.recoverPublicKey(hash);
const P = sigObjP.recoverPublicKey(hash);
return [Q.toRawBytes().subarray(1, 33), P.toRawBytes().subarray(1, 33)];
};

export const uint8ArrayToBigInt = (uint8Array: Uint8Array) => {
const array = Array.from(uint8Array);
return BigInt('0x' + array.map(byte => byte.toString(16).padStart(2, '0')).join(''));
};
2 changes: 1 addition & 1 deletion packages/providers/test/providers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe('providers', () => {

// TODO: Use a near-workspaces Worker when time traveling is available
test('json rpc get next light client block', async () => {
const provider = new JsonRpcProvider({ url: 'https://rpc.ci-testnet.near.org' });
const provider = new JsonRpcProvider({ url: 'https://rpc.testnet.near.org' });
const stat = await provider.status();

// Get block in at least the last epoch (epoch duration 43,200 blocks on mainnet and testnet)
Expand Down
38 changes: 4 additions & 34 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 265b497

Please sign in to comment.