Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Biometric-ed25519 package bug fix #1309

Merged
merged 6 commits into from
Mar 7, 2024
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
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;
vikinatora marked this conversation as resolved.
Show resolved Hide resolved
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);
vikinatora marked this conversation as resolved.
Show resolved Hide resolved
};

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)];
vikinatora marked this conversation as resolved.
Show resolved Hide resolved
};

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.

Loading