From 455ae2ccde501229fd88ba2d20552eacf19e1400 Mon Sep 17 00:00:00 2001 From: Jaco Date: Wed, 12 Apr 2023 12:58:17 +0300 Subject: [PATCH] Use @noble/curves (secp256k1 + ed25519) (#1776) * Use @noble/curves (secp256k1 only for now) * Re-add canonical signatures (now lowS) * Swap tweetnacl usage for ed25519 * Adjust previously failing tests * Adjust test for ethereum encoding * hasBigInt for ed25519 operations * CHANGELOG * Publich beta to npm for size check --- .123npm | 0 CHANGELOG.md | 9 +++++++ packages/util-crypto/package.json | 5 ++-- packages/util-crypto/src/bundleInit.ts | 10 ------- .../src/ed25519/pair/fromSecret.ts | 13 +++++++--- .../util-crypto/src/ed25519/pair/fromSeed.ts | 12 ++++++--- packages/util-crypto/src/ed25519/sign.ts | 13 ++++++---- packages/util-crypto/src/ed25519/verify.ts | 14 ++++++---- .../util-crypto/src/secp256k1/compress.ts | 4 +-- packages/util-crypto/src/secp256k1/expand.ts | 8 +++--- .../src/secp256k1/pair/fromSeed.ts | 4 +-- packages/util-crypto/src/secp256k1/recover.ts | 8 ++++-- packages/util-crypto/src/secp256k1/sign.ts | 11 ++++---- yarn.lock | 26 +++++++------------ 14 files changed, 75 insertions(+), 62 deletions(-) create mode 100644 .123npm diff --git a/.123npm b/.123npm new file mode 100644 index 000000000..e69de29bb diff --git a/CHANGELOG.md b/CHANGELOG.md index acb227a0d..29718631b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## master + +Changes: + +- Swap secp256k1 functionality to `@noble/curves` +- Swap ed25519 functionality to `@noble/curves` +- Replace `tweetnacl` secretbox usage with slimmer local version + + ## 11.1.3 Apr 1, 2023 Contributed: diff --git a/packages/util-crypto/package.json b/packages/util-crypto/package.json index 3373869ab..dc18048de 100644 --- a/packages/util-crypto/package.json +++ b/packages/util-crypto/package.json @@ -27,16 +27,15 @@ }, "main": "index.js", "dependencies": { + "@noble/curves": "1.0.0", "@noble/hashes": "1.3.0", - "@noble/secp256k1": "1.7.1", "@polkadot/networks": "11.1.4-6-x", "@polkadot/util": "11.1.4-6-x", "@polkadot/wasm-crypto": "^7.0.3", "@polkadot/x-bigint": "11.1.4-6-x", "@polkadot/x-randomvalues": "11.1.4-6-x", "@scure/base": "1.1.1", - "tslib": "^2.5.0", - "tweetnacl": "^1.0.3" + "tslib": "^2.5.0" }, "peerDependencies": { "@polkadot/util": "11.1.4-6-x" diff --git a/packages/util-crypto/src/bundleInit.ts b/packages/util-crypto/src/bundleInit.ts index 09ba16325..48a3cd9e1 100644 --- a/packages/util-crypto/src/bundleInit.ts +++ b/packages/util-crypto/src/bundleInit.ts @@ -3,18 +3,8 @@ import '@polkadot/x-bigint/shim'; -import { utils as utilsNobleSecp256k1 } from '@noble/secp256k1'; - -import { u8aConcat } from '@polkadot/util'; - -import { hmacSha256AsU8a } from './hmac/index.js'; import { cryptoWaitReady } from './crypto.js'; -// Set overrides on the secp256k1 utils -// - hmacShaSync - This needs to be set, unset by default -utilsNobleSecp256k1.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]) => - hmacSha256AsU8a(key, u8aConcat(...messages)); - // start init process immediately cryptoWaitReady().catch((): void => { // shouldn't happen, logged and caught inside cryptoWaitReady diff --git a/packages/util-crypto/src/ed25519/pair/fromSecret.ts b/packages/util-crypto/src/ed25519/pair/fromSecret.ts index 709829aaf..f3c63b70a 100644 --- a/packages/util-crypto/src/ed25519/pair/fromSecret.ts +++ b/packages/util-crypto/src/ed25519/pair/fromSecret.ts @@ -3,8 +3,6 @@ import type { Keypair } from '../../types.js'; -import nacl from 'tweetnacl'; - /** * @name ed25519PairFromSecret * @summary Creates a new public/secret keypair from a secret. @@ -19,6 +17,13 @@ import nacl from 'tweetnacl'; * ed25519PairFromSecret(...); // => { secretKey: [...], publicKey: [...] } * ``` */ -export function ed25519PairFromSecret (secret: Uint8Array): Keypair { - return nacl.sign.keyPair.fromSecretKey(secret); +export function ed25519PairFromSecret (secretKey: Uint8Array): Keypair { + if (secretKey.length !== 64) { + throw new Error('Invalid secretKey provided'); + } + + return { + publicKey: secretKey.slice(32), + secretKey + }; } diff --git a/packages/util-crypto/src/ed25519/pair/fromSeed.ts b/packages/util-crypto/src/ed25519/pair/fromSeed.ts index 98fce86b6..546e65774 100644 --- a/packages/util-crypto/src/ed25519/pair/fromSeed.ts +++ b/packages/util-crypto/src/ed25519/pair/fromSeed.ts @@ -3,8 +3,9 @@ import type { Keypair } from '../../types.js'; -import nacl from 'tweetnacl'; +import { ed25519 } from '@noble/curves/ed25519'; +import { hasBigInt, u8aConcatStrict } from '@polkadot/util'; import { ed25519KeypairFromSeed, isReady } from '@polkadot/wasm-crypto'; /** @@ -22,7 +23,7 @@ import { ed25519KeypairFromSeed, isReady } from '@polkadot/wasm-crypto'; * ``` */ export function ed25519PairFromSeed (seed: Uint8Array, onlyJs?: boolean): Keypair { - if (!onlyJs && isReady()) { + if (!hasBigInt || (!onlyJs && isReady())) { const full = ed25519KeypairFromSeed(seed); return { @@ -31,5 +32,10 @@ export function ed25519PairFromSeed (seed: Uint8Array, onlyJs?: boolean): Keypai }; } - return nacl.sign.keyPair.fromSeed(seed); + const publicKey = ed25519.getPublicKey(seed); + + return { + publicKey, + secretKey: u8aConcatStrict([seed, publicKey]) + }; } diff --git a/packages/util-crypto/src/ed25519/sign.ts b/packages/util-crypto/src/ed25519/sign.ts index e096ce406..9fd100d25 100644 --- a/packages/util-crypto/src/ed25519/sign.ts +++ b/packages/util-crypto/src/ed25519/sign.ts @@ -4,9 +4,9 @@ import type { HexString } from '@polkadot/util/types'; import type { Keypair } from '../types.js'; -import nacl from 'tweetnacl'; +import { ed25519 } from '@noble/curves/ed25519'; -import { u8aToU8a } from '@polkadot/util'; +import { hasBigInt, u8aToU8a } from '@polkadot/util'; import { ed25519Sign as wasmSign, isReady } from '@polkadot/wasm-crypto'; /** @@ -26,11 +26,14 @@ import { ed25519Sign as wasmSign, isReady } from '@polkadot/wasm-crypto'; export function ed25519Sign (message: HexString | Uint8Array | string, { publicKey, secretKey }: Partial, onlyJs?: boolean): Uint8Array { if (!secretKey) { throw new Error('Expected a valid secretKey'); + } else if (!publicKey) { + throw new Error('Expected a valid publicKey'); } const messageU8a = u8aToU8a(message); + const privateU8a = secretKey.subarray(0, 32); - return !onlyJs && isReady() - ? wasmSign(publicKey as Uint8Array, secretKey.subarray(0, 32), messageU8a) - : nacl.sign.detached(messageU8a, secretKey); + return !hasBigInt || (!onlyJs && isReady()) + ? wasmSign(publicKey, privateU8a, messageU8a) + : ed25519.sign(messageU8a, privateU8a); } diff --git a/packages/util-crypto/src/ed25519/verify.ts b/packages/util-crypto/src/ed25519/verify.ts index e8008002d..ff6d5a34f 100644 --- a/packages/util-crypto/src/ed25519/verify.ts +++ b/packages/util-crypto/src/ed25519/verify.ts @@ -3,9 +3,9 @@ import type { HexString } from '@polkadot/util/types'; -import nacl from 'tweetnacl'; +import { ed25519 } from '@noble/curves/ed25519'; -import { u8aToU8a } from '@polkadot/util'; +import { hasBigInt, u8aToU8a } from '@polkadot/util'; import { ed25519Verify as wasmVerify, isReady } from '@polkadot/wasm-crypto'; /** @@ -33,7 +33,11 @@ export function ed25519Verify (message: HexString | Uint8Array | string, signatu throw new Error(`Invalid signature, received ${signatureU8a.length} bytes, expected 64`); } - return !onlyJs && isReady() - ? wasmVerify(signatureU8a, messageU8a, publicKeyU8a) - : nacl.sign.detached.verify(messageU8a, signatureU8a, publicKeyU8a); + try { + return !hasBigInt || (!onlyJs && isReady()) + ? wasmVerify(signatureU8a, messageU8a, publicKeyU8a) + : ed25519.verify(signatureU8a, messageU8a, publicKeyU8a); + } catch { + return false; + } } diff --git a/packages/util-crypto/src/secp256k1/compress.ts b/packages/util-crypto/src/secp256k1/compress.ts index 1fcc4c18d..78b9ecba7 100644 --- a/packages/util-crypto/src/secp256k1/compress.ts +++ b/packages/util-crypto/src/secp256k1/compress.ts @@ -1,7 +1,7 @@ // Copyright 2017-2023 @polkadot/util-crypto authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { Point } from '@noble/secp256k1'; +import { secp256k1 } from '@noble/curves/secp256k1'; import { hasBigInt } from '@polkadot/util'; import { isReady, secp256k1Compress as wasm } from '@polkadot/wasm-crypto'; @@ -17,5 +17,5 @@ export function secp256k1Compress (publicKey: Uint8Array, onlyJs?: boolean): Uin return !hasBigInt || (!onlyJs && isReady()) ? wasm(publicKey) - : Point.fromHex(publicKey).toRawBytes(true); + : secp256k1.ProjectivePoint.fromHex(publicKey).toRawBytes(true); } diff --git a/packages/util-crypto/src/secp256k1/expand.ts b/packages/util-crypto/src/secp256k1/expand.ts index a72ff3b56..68e27bc5d 100644 --- a/packages/util-crypto/src/secp256k1/expand.ts +++ b/packages/util-crypto/src/secp256k1/expand.ts @@ -1,7 +1,7 @@ // Copyright 2017-2023 @polkadot/util-crypto authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { Point } from '@noble/secp256k1'; +import { secp256k1 } from '@noble/curves/secp256k1'; import { bnToU8a, hasBigInt, u8aConcat } from '@polkadot/util'; import { isReady, secp256k1Expand as wasm } from '@polkadot/wasm-crypto'; @@ -21,10 +21,10 @@ export function secp256k1Expand (publicKey: Uint8Array, onlyJs?: boolean): Uint8 return wasm(publicKey).subarray(1); } - const { x, y } = Point.fromHex(publicKey); + const { px, py } = secp256k1.ProjectivePoint.fromHex(publicKey); return u8aConcat( - bnToU8a(x, BN_BE_256_OPTS), - bnToU8a(y, BN_BE_256_OPTS) + bnToU8a(px, BN_BE_256_OPTS), + bnToU8a(py, BN_BE_256_OPTS) ); } diff --git a/packages/util-crypto/src/secp256k1/pair/fromSeed.ts b/packages/util-crypto/src/secp256k1/pair/fromSeed.ts index 0ee7fb59f..eaa6484d2 100644 --- a/packages/util-crypto/src/secp256k1/pair/fromSeed.ts +++ b/packages/util-crypto/src/secp256k1/pair/fromSeed.ts @@ -3,7 +3,7 @@ import type { Keypair } from '../../types.js'; -import { getPublicKey } from '@noble/secp256k1'; +import { secp256k1 } from '@noble/curves/secp256k1'; import { hasBigInt, u8aEmpty } from '@polkadot/util'; import { isReady, secp256k1FromSeed } from '@polkadot/wasm-crypto'; @@ -36,7 +36,7 @@ export function secp256k1PairFromSeed (seed: Uint8Array, onlyJs?: boolean): Keyp } return { - publicKey: getPublicKey(seed, true), + publicKey: secp256k1.getPublicKey(seed, true), secretKey: seed }; } diff --git a/packages/util-crypto/src/secp256k1/recover.ts b/packages/util-crypto/src/secp256k1/recover.ts index 82e52e00b..89e86bf20 100644 --- a/packages/util-crypto/src/secp256k1/recover.ts +++ b/packages/util-crypto/src/secp256k1/recover.ts @@ -4,7 +4,7 @@ import type { HexString } from '@polkadot/util/types'; import type { HashType } from './types.js'; -import { recoverPublicKey, Signature } from '@noble/secp256k1'; +import { secp256k1 } from '@noble/curves/secp256k1'; import { hasBigInt, u8aToU8a } from '@polkadot/util'; import { isReady, secp256k1Recover as wasm } from '@polkadot/wasm-crypto'; @@ -21,7 +21,11 @@ export function secp256k1Recover (msgHash: HexString | Uint8Array | string, sign const msg = u8aToU8a(msgHash); const publicKey = !hasBigInt || (!onlyJs && isReady()) ? wasm(msg, sig, recovery) - : recoverPublicKey(msg, Signature.fromCompact(sig).toRawBytes(), recovery); + : secp256k1.Signature + .fromCompact(sig) + .addRecoveryBit(recovery) + .recoverPublicKey(msg) + .toRawBytes(); if (!publicKey) { throw new Error('Unable to recover publicKey from signature'); diff --git a/packages/util-crypto/src/secp256k1/sign.ts b/packages/util-crypto/src/secp256k1/sign.ts index d796f2b91..19cb70c08 100644 --- a/packages/util-crypto/src/secp256k1/sign.ts +++ b/packages/util-crypto/src/secp256k1/sign.ts @@ -4,7 +4,7 @@ import type { Keypair } from '../types.js'; import type { HashType } from './types.js'; -import { Signature, signSync } from '@noble/secp256k1'; +import { secp256k1 } from '@noble/curves/secp256k1'; import { bnToU8a, hasBigInt, u8aConcat } from '@polkadot/util'; import { isReady, secp256k1Sign as wasm } from '@polkadot/wasm-crypto'; @@ -27,12 +27,11 @@ export function secp256k1Sign (message: Uint8Array | string, { secretKey }: Part return wasm(data, secretKey); } - const [sigBytes, recoveryParam] = signSync(data, secretKey, { canonical: true, recovered: true }); - const { r, s } = Signature.fromHex(sigBytes); + const signature = secp256k1.sign(data, secretKey, { lowS: true }); return u8aConcat( - bnToU8a(r, BN_BE_256_OPTS), - bnToU8a(s, BN_BE_256_OPTS), - new Uint8Array([recoveryParam || 0]) + bnToU8a(signature.r, BN_BE_256_OPTS), + bnToU8a(signature.s, BN_BE_256_OPTS), + new Uint8Array([signature.recovery || 0]) ); } diff --git a/yarn.lock b/yarn.lock index 8a3240829..4055a408b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -314,6 +314,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.0.0": + version: 1.0.0 + resolution: "@noble/curves@npm:1.0.0" + dependencies: + "@noble/hashes": 1.3.0 + checksum: 6bcef44d626c640dc8961819d68dd67dffb907e3b973b7c27efe0ecdd9a5c6ce62c7b9e3dfc930c66605dced7f1ec0514d191c09a2ce98d6d52b66e3315ffa79 + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.0": version: 1.3.0 resolution: "@noble/hashes@npm:1.3.0" @@ -321,13 +330,6 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:1.7.1": - version: 1.7.1 - resolution: "@noble/secp256k1@npm:1.7.1" - checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -634,8 +636,8 @@ __metadata: version: 0.0.0-use.local resolution: "@polkadot/util-crypto@workspace:packages/util-crypto" dependencies: + "@noble/curves": 1.0.0 "@noble/hashes": 1.3.0 - "@noble/secp256k1": 1.7.1 "@polkadot/networks": 11.1.4-6-x "@polkadot/util": 11.1.4-6-x "@polkadot/wasm-crypto": ^7.0.3 @@ -643,7 +645,6 @@ __metadata: "@polkadot/x-randomvalues": 11.1.4-6-x "@scure/base": 1.1.1 tslib: ^2.5.0 - tweetnacl: ^1.0.3 peerDependencies: "@polkadot/util": 11.1.4-6-x languageName: unknown @@ -8624,13 +8625,6 @@ fsevents@~2.3.2: languageName: node linkType: hard -"tweetnacl@npm:^1.0.3": - version: 1.0.3 - resolution: "tweetnacl@npm:1.0.3" - checksum: e4a57cac188f0c53f24c7a33279e223618a2bfb5fea426231991652a13247bea06b081fd745d71291fcae0f4428d29beba1b984b1f1ce6f66b06a6d1ab90645c - languageName: node - linkType: hard - "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0"