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
This commit is contained in:
Jaco
2023-04-12 12:58:17 +03:00
committed by GitHub
parent 9d43b787fd
commit 455ae2ccde
14 changed files with 75 additions and 62 deletions
-10
View File
@@ -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
@@ -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
};
}
@@ -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])
};
}
+8 -5
View File
@@ -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<Keypair>, 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);
}
+9 -5
View File
@@ -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;
}
}
@@ -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);
}
+4 -4
View File
@@ -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)
);
}
@@ -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
};
}
@@ -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');
+5 -6
View File
@@ -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])
);
}