diff --git a/.changeset/cuddly-plants-wave.md b/.changeset/cuddly-plants-wave.md new file mode 100644 index 0000000000..b482efec4d --- /dev/null +++ b/.changeset/cuddly-plants-wave.md @@ -0,0 +1,5 @@ +--- +"@near-js/biometric-ed25519": patch +--- + +Fix recoverPublicKey(), createKey() and getKeys() methods. diff --git a/packages/biometric-ed25519/package.json b/packages/biometric-ed25519/package.json index 3e0dad25a7..e4b9fe6ec8 100644 --- a/packages/biometric-ed25519/package.json +++ b/packages/biometric-ed25519/package.json @@ -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" } } diff --git a/packages/biometric-ed25519/src/index.ts b/packages/biometric-ed25519/src/index.ts index 57b31af01e..f0016e4511 100644 --- a/packages/biometric-ed25519/src/index.ts +++ b/packages/biometric-ed25519/src/index.ts @@ -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'; @@ -11,7 +11,8 @@ import { get64BytePublicKeyFromPEM, preformatGetAssertReq, publicKeyCredentialToJSON, - recoverPublicKey + recoverPublicKey, + uint8ArrayToBigInt } from './utils'; import { Fido2 } from './fido2'; import { AssertionResponse } from './index.d'; @@ -69,11 +70,9 @@ export const createKey = async (username: string): Promise => { }); 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)])))); }); }; @@ -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]; }); }; diff --git a/packages/biometric-ed25519/src/utils.ts b/packages/biometric-ed25519/src/utils.ts index eed9457067..c9b5647a3e 100644 --- a/packages/biometric-ed25519/src/utils.ts +++ b/packages/biometric-ed25519/src/utils.ts @@ -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) => { @@ -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 => { @@ -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('')); +}; \ No newline at end of file diff --git a/packages/providers/test/providers.test.js b/packages/providers/test/providers.test.js index 509860b9e3..f66a34c5a1 100644 --- a/packages/providers/test/providers.test.js +++ b/packages/providers/test/providers.test.js @@ -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) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e319d6e3f1..0b4e85e6b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,9 +117,6 @@ importers: packages/biometric-ed25519: dependencies: - '@aws-crypto/sha256-js': - specifier: 4.0.0 - version: 4.0.0 '@hexagon/base64': specifier: 1.1.26 version: 1.1.26 @@ -132,6 +129,9 @@ importers: '@noble/curves': specifier: 1.2.0 version: 1.2.0 + '@noble/hashes': + specifier: 1.3.3 + version: 1.3.3 asn1-parser: specifier: 1.1.8 version: 1.1.8 @@ -148,9 +148,6 @@ importers: specifier: 3.4.1 version: 3.4.1 devDependencies: - '@aws-sdk/types': - specifier: 3.329.0 - version: 3.329.0 '@types/node': specifier: 18.11.18 version: 18.11.18 @@ -627,34 +624,6 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@aws-crypto/sha256-js@4.0.0: - resolution: {integrity: sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==} - dependencies: - '@aws-crypto/util': 4.0.0 - '@aws-sdk/types': 3.329.0 - tslib: 1.14.1 - dev: false - - /@aws-crypto/util@4.0.0: - resolution: {integrity: sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==} - dependencies: - '@aws-sdk/types': 3.329.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - dev: false - - /@aws-sdk/types@3.329.0: - resolution: {integrity: sha512-wFBW4yciDfzQBSFmWNaEvHShnSGLMxSu9Lls6EUf6xDMavxSB36bsrVRX6CyAo/W0NeIIyEOW1LclGPgJV1okg==} - engines: {node: '>=14.0.0'} - dependencies: - tslib: 2.6.1 - - /@aws-sdk/util-utf8-browser@3.259.0: - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} - dependencies: - tslib: 2.6.1 - dev: false - /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} @@ -8446,6 +8415,7 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true /tslib@2.6.1: resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==}