diff --git a/eslint.config.mjs b/eslint.config.mjs index 657ff75..9114b6a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -112,13 +112,6 @@ const SwitchDebtFiles = [ "packages/sdk-core/src/chain/PublicKey.ts", "packages/sdk-core/src/chain/Signature.ts", "packages/sdk-core/src/chain/Transaction.ts", - "packages/sdk-core/src/crypto/Curves.ts", - "packages/sdk-core/src/crypto/Generate.ts", - "packages/sdk-core/src/crypto/GetPublic.ts", - "packages/sdk-core/src/crypto/Recover.ts", - "packages/sdk-core/src/crypto/SharedSecret.ts", - "packages/sdk-core/src/crypto/Sign.ts", - "packages/sdk-core/src/crypto/Verify.ts", "packages/sdk-core/src/serializer/Builtins.ts", "packages/sdk-core/src/serializer/Decoder.ts", "packages/sdk-core/src/serializer/Encoder.ts", @@ -166,7 +159,6 @@ const InlineTypeLiteralDebtFiles = [ "packages/sdk-core/src/contracts/sysio/msig/Status.ts", "packages/sdk-core/src/contracts/sysio/msig/Transaction.ts", "packages/sdk-core/src/contracts/sysio/msig/Types.ts", - "packages/sdk-core/src/crypto/Curves.ts", "packages/sdk-core/src/p2p/Client.ts", "packages/sdk-core/src/p2p/Provider.ts", "packages/sdk-core/src/serializer/Builtins.ts", @@ -223,7 +215,6 @@ const RuleDebt = [ "examples/web-logging-example/src/web-logging-example.ts", "packages/sdk-core/src/Utils.ts", "packages/sdk-core/src/chain/PrivateKey.ts", - "packages/sdk-core/src/crypto/Sign.ts", "packages/sdk-core/tests/chain/Abi.test.ts", "packages/sdk-core/tests/chain/Asset.test.ts", "packages/sdk-core/tests/crypto/crypto.test.ts", @@ -246,8 +237,7 @@ const RuleDebt = [ "packages/sdk-core/src/api/Client.ts", "packages/sdk-core/src/chain/Abi.ts", "packages/sdk-core/src/chain/Bytes.ts", - "packages/sdk-core/src/chain/PublicKey.ts", - "packages/sdk-core/src/crypto/Sign.ts" + "packages/sdk-core/src/chain/PublicKey.ts" ] }, { diff --git a/packages/sdk-core/src/crypto/Curves.ts b/packages/sdk-core/src/crypto/Curves.ts index 5ba6095..544b34a 100644 --- a/packages/sdk-core/src/crypto/Curves.ts +++ b/packages/sdk-core/src/crypto/Curves.ts @@ -1,10 +1,17 @@ import { p256 } from "@noble/curves/nist.js" import { secp256k1 } from "@noble/curves/secp256k1.js" import { ec } from "elliptic" +import { match } from "ts-pattern" import { KeyType } from "../chain/KeyType.js" -const curves: { [type: string]: ec } = {} -const nobleCurves: { [type: string]: typeof secp256k1 } = {} +/** Cache of elliptic curve instances keyed by Wire key type. */ +type EllipticCurveByKeyType = Partial> + +/** Cache of Noble Weierstrass curve modules keyed by Wire key type. */ +type NobleCurveByKeyType = Partial> + +const curves: EllipticCurveByKeyType = {} +const nobleCurves: NobleCurveByKeyType = {} /** * Get curve for key type. @@ -14,21 +21,17 @@ export function getCurve(type: KeyType): ec { let rv = curves[type] if (!rv) { - switch (type) { - case KeyType.K1: - case KeyType.EM: - rv = curves[type] = new ec("secp256k1") - break - case KeyType.R1: - rv = curves[type] = new ec("p256") - break - case KeyType.ED: + rv = curves[type] = match(type) + .with(KeyType.K1, KeyType.EM, () => new ec("secp256k1")) + .with(KeyType.R1, () => new ec("p256")) + .with(KeyType.ED, () => { throw new Error( "ED25519 keys are not supported via elliptic; use libsodium for ED-based operations" ) - default: + }) + .otherwise(() => { throw new Error(`Unknown curve type: ${type}`) - } + }) } return rv @@ -42,21 +45,17 @@ export function getNobleCurve(type: KeyType): typeof secp256k1 { let curve = nobleCurves[type] if (!curve) { - switch (type) { - case KeyType.K1: - case KeyType.EM: - curve = nobleCurves[type] = secp256k1 - break - case KeyType.R1: - curve = nobleCurves[type] = p256 - break - case KeyType.ED: + curve = nobleCurves[type] = match(type) + .with(KeyType.K1, KeyType.EM, () => secp256k1) + .with(KeyType.R1, () => p256) + .with(KeyType.ED, () => { throw new Error( "ED25519 keys are not supported via Noble Weierstrass curves" ) - default: + }) + .otherwise(() => { throw new Error(`Unknown curve type: ${type}`) - } + }) } return curve diff --git a/packages/sdk-core/src/crypto/Generate.ts b/packages/sdk-core/src/crypto/Generate.ts index 47c65b9..d88b819 100644 --- a/packages/sdk-core/src/crypto/Generate.ts +++ b/packages/sdk-core/src/crypto/Generate.ts @@ -1,5 +1,6 @@ // src/crypto/generate.ts +import { match } from "ts-pattern" import { KeyType } from "../chain/KeyType.js" import { getNobleCurve } from "./Curves.js" import nacl from "tweetnacl" @@ -10,19 +11,9 @@ import { blsGenerate, skToLE } from "./BLS.js" * @internal */ export function generate(type: KeyType): Uint8Array { - switch (type) { - case KeyType.ED: // ED25519 private key via tweetnacl - return nacl.sign.keyPair().secretKey // 64-byte secretKey = 32b seed + 32b pubkey - - case KeyType.EM: { - return getNobleCurve(type).utils.randomSecretKey() - } - - case KeyType.BLS: - return skToLE(blsGenerate()) - - default: { - return getNobleCurve(type).utils.randomSecretKey() - } - } + return match(type) + .with(KeyType.ED, () => nacl.sign.keyPair().secretKey) + .with(KeyType.EM, () => getNobleCurve(type).utils.randomSecretKey()) + .with(KeyType.BLS, () => skToLE(blsGenerate())) + .otherwise(() => getNobleCurve(type).utils.randomSecretKey()) } diff --git a/packages/sdk-core/src/crypto/GetPublic.ts b/packages/sdk-core/src/crypto/GetPublic.ts index 9a70b7e..45eade4 100644 --- a/packages/sdk-core/src/crypto/GetPublic.ts +++ b/packages/sdk-core/src/crypto/GetPublic.ts @@ -1,3 +1,4 @@ +import { match } from "ts-pattern" import { KeyType } from "../chain/KeyType.js" import { getNobleCurve } from "./Curves.js" import nacl from "tweetnacl" @@ -8,15 +9,8 @@ import { blsGetPublicKey, skFromLE } from "./BLS.js" * @internal */ export function getPublic(privkey: Uint8Array, type: KeyType) { - switch (type) { - case KeyType.ED: // Derive ED25519 public key via tweetnacl - return nacl.sign.keyPair.fromSecretKey(privkey).publicKey - - case KeyType.BLS: - return blsGetPublicKey(skFromLE(privkey)) - - default: { - return getNobleCurve(type).getPublicKey(privkey, true) - } - } + return match(type) + .with(KeyType.ED, () => nacl.sign.keyPair.fromSecretKey(privkey).publicKey) + .with(KeyType.BLS, () => blsGetPublicKey(skFromLE(privkey))) + .otherwise(() => getNobleCurve(type).getPublicKey(privkey, true)) } diff --git a/packages/sdk-core/src/crypto/Recover.ts b/packages/sdk-core/src/crypto/Recover.ts index f3cffbf..c0b2ae5 100644 --- a/packages/sdk-core/src/crypto/Recover.ts +++ b/packages/sdk-core/src/crypto/Recover.ts @@ -1,3 +1,4 @@ +import { match } from "ts-pattern" import { ethers } from "../EthersCompat.js" import { Bytes } from "../chain/Bytes.js" import { KeyType } from "../chain/KeyType.js" @@ -19,14 +20,14 @@ export function recover( message: Uint8Array, type: KeyType ): PublicKey { - switch (type) { - case KeyType.ED: + return match(type) + .with(KeyType.ED, () => { throw new Error("ED25519 does not support public key recovery") - - case KeyType.BLS: + }) + .with(KeyType.BLS, () => { throw new Error("BLS does not support public key recovery") - - case KeyType.EM: { + }) + .with(KeyType.EM, () => { // wire: [vWire(31/32)‖r(32)‖s(32)] const vRaw = signature[0] - 4 // 27/28 const r = signature.subarray(1, 33) @@ -40,9 +41,8 @@ export function recover( KeyType.EM, new Bytes(ethers.utils.arrayify(compressed)) ) - } - - default: { + }) + .otherwise(() => { // wire: [vWire(31/32)‖r‖s] const recid = signature[0] - 31 const curve = getNobleCurve(type) @@ -55,6 +55,5 @@ export function recover( .recoverPublicKey(message) .toBytes(true) return new PublicKey(type, new Bytes(compressed)) - } - } + }) } diff --git a/packages/sdk-core/src/crypto/SharedSecret.ts b/packages/sdk-core/src/crypto/SharedSecret.ts index a74df62..e5225ad 100644 --- a/packages/sdk-core/src/crypto/SharedSecret.ts +++ b/packages/sdk-core/src/crypto/SharedSecret.ts @@ -1,5 +1,6 @@ // src/crypto/shared-secret.ts +import { match } from "ts-pattern" import { KeyType } from "../chain/KeyType.js" import { getNobleCurve } from "./Curves.js" @@ -21,17 +22,17 @@ export function sharedSecret( pubkey: Uint8Array, type: KeyType ): Uint8Array { - switch (type) { - case KeyType.ED: + return match(type) + .with(KeyType.ED, () => { // ED25519 does not support ECDH-derived shared secrets throw new Error( "Shared secret (ECDH) not supported for ED25519; convert to X25519 first" ) - - case KeyType.BLS: + }) + .with(KeyType.BLS, () => { throw new Error("BLS does not support shared secret") - - default: { + }) + .otherwise(() => { const sharedPoint = getNobleCurve(type).getSharedSecret( privkey, pubkey, @@ -40,6 +41,5 @@ export function sharedSecret( return trimLeadingZeroBytes( sharedPoint.subarray(COMPRESSED_PUBLIC_KEY_PREFIX_BYTES) ) - } - } + }) } diff --git a/packages/sdk-core/src/crypto/Sign.ts b/packages/sdk-core/src/crypto/Sign.ts index 2ca5f0c..1818ab9 100644 --- a/packages/sdk-core/src/crypto/Sign.ts +++ b/packages/sdk-core/src/crypto/Sign.ts @@ -5,7 +5,7 @@ import nacl from "tweetnacl" import { isObject } from "@wireio/shared" import { ethers } from "../EthersCompat.js" import { blsSign, skFromLE } from "./BLS.js" -import { asOption, Option } from "@3fv/prelude-ts" +import { asOption } from "@3fv/prelude-ts" import { match } from "ts-pattern" import { defaults } from "lodash" import { ChainKind } from "@wireio/opp-typescript-models" @@ -34,16 +34,15 @@ export function sign( type: KeyType, options: sign.Options | null = null ): SignatureParts { - switch (type) { - case KeyType.ED: { + return match(type) + .with(KeyType.ED, () => { // ED25519 detached signature via tweetnacl const sigBytes = nacl.sign.detached(message, secret) const r = sigBytes.slice(0, 32) const s = sigBytes.slice(32, 64) return { type, r, s, recid: 0 } - } - - case KeyType.EM: { + }) + .with(KeyType.EM, () => { // Ethereum signature using EIP-191 prefix by default. const ethOptions = sign.getOptions(options, ChainKind.EVM) const hash = ethOptions.personalMessage @@ -57,15 +56,13 @@ export function sign( // vWire = recoveryParam + 31 = ethV + 4 for EM signatures. const recid = sig.recoveryParam! return { type, r, s, recid } - } - - case KeyType.BLS: { + }) + .with(KeyType.BLS, () => { const skBE = skFromLE(secret) const sigLE = blsSign(skBE, message) return { type, r: sigLE, s: new Uint8Array(0), recid: 0 } - } - - case KeyType.K1: { + }) + .with(KeyType.K1, () => { const curve = getCurve(type) const key = curve.keyFromPrivate(secret) let sig: ReturnType @@ -88,9 +85,8 @@ export function sign( } while (!isCanonical(r, s)) return { type, r, s, recid: sig.recoveryParam || 0 } - } - - default: { + }) + .otherwise(() => { const signature = getNobleCurve(type).sign(message, secret, { lowS: true }) @@ -100,8 +96,7 @@ export function sign( s: numberToBytesBE(signature.s, SIGNATURE_COMPONENT_BYTES), recid: signature.recovery } - } - } + }) } export namespace sign { @@ -134,7 +129,7 @@ export namespace sign { .map(o => o as Options) .getOrElse({ chain } as Options), defaultOpts = - chain == ChainKind.EVM ? DefaultEthereumOptions : DefaultBaseOptions + chain === ChainKind.EVM ? DefaultEthereumOptions : DefaultBaseOptions return defaults(opts, defaultOpts) } diff --git a/packages/sdk-core/src/crypto/Verify.ts b/packages/sdk-core/src/crypto/Verify.ts index b983bd3..cda03e1 100644 --- a/packages/sdk-core/src/crypto/Verify.ts +++ b/packages/sdk-core/src/crypto/Verify.ts @@ -3,6 +3,7 @@ import { KeyType } from "../chain/KeyType.js" import nacl from "tweetnacl" import { ethers } from "../EthersCompat.js" import { blsVerify } from "./BLS.js" +import { match } from "ts-pattern" /** * Verify signature using message and public key. @@ -19,26 +20,22 @@ export function verify( pubkey: Uint8Array, type: KeyType ): boolean { - switch (type) { - case KeyType.ED: // ED25519 detached verification via tweetnacl - return nacl.sign.detached.verify(message, signature, pubkey) - - case KeyType.EM: { + return match(type) + .with(KeyType.ED, () => + nacl.sign.detached.verify(message, signature, pubkey) + ) + .with(KeyType.EM, () => { const sigBytes = ethers.utils.arrayify(signature) const msgBytes = ethers.utils.arrayify(message) const recovered = ethers.utils.verifyMessage(msgBytes, sigBytes) const expected = ethers.utils.computeAddress(pubkey) return recovered.toLowerCase() === expected.toLowerCase() - } - - case KeyType.BLS: - return blsVerify(signature, message, pubkey) - - default: { + }) + .with(KeyType.BLS, () => blsVerify(signature, message, pubkey)) + .otherwise(() => { const compactSignature = signature.subarray(1) return getNobleCurve(type).verify(compactSignature, message, pubkey, { lowS: false }) - } - } + }) }