Skip to content

Commit dd7d17c

Browse files
authored
fix: allow keys to do sync sign/verify (#2258)
Removes unnecessary async from Ed/Secp keys sign/verify methods. Ed is sync everywhere, Secp is sync in node but async in browsers.
1 parent 7877a50 commit dd7d17c

File tree

10 files changed

+125
-59
lines changed

10 files changed

+125
-59
lines changed

packages/crypto/src/keys/ed25519-class.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { base58btc } from 'multiformats/bases/base58'
33
import { identity } from 'multiformats/hashes/identity'
44
import { sha256 } from 'multiformats/hashes/sha2'
55
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
6+
import { isPromise } from '../util.js'
67
import * as crypto from './ed25519.js'
78
import { exporter } from './exporter.js'
89
import * as pbm from './keys.js'
@@ -16,7 +17,7 @@ export class Ed25519PublicKey {
1617
this._key = ensureKey(key, crypto.publicKeyLength)
1718
}
1819

19-
async verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): Promise<boolean> {
20+
verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): boolean {
2021
return crypto.hashAndVerify(this._key, sig, data)
2122
}
2223

@@ -35,10 +36,14 @@ export class Ed25519PublicKey {
3536
return uint8ArrayEquals(this.bytes, key.bytes)
3637
}
3738

38-
async hash (): Promise<Uint8Array> {
39-
const { bytes } = await sha256.digest(this.bytes)
39+
hash (): Uint8Array | Promise<Uint8Array> {
40+
const p = sha256.digest(this.bytes)
4041

41-
return bytes
42+
if (isPromise(p)) {
43+
return p.then(({ bytes }) => bytes)
44+
}
45+
46+
return p.bytes
4247
}
4348
}
4449

@@ -53,7 +58,7 @@ export class Ed25519PrivateKey {
5358
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
5459
}
5560

56-
async sign (message: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
61+
sign (message: Uint8Array | Uint8ArrayList): Uint8Array {
5762
return crypto.hashAndSign(this._key, message)
5863
}
5964

@@ -77,7 +82,14 @@ export class Ed25519PrivateKey {
7782
}
7883

7984
async hash (): Promise<Uint8Array> {
80-
const { bytes } = await sha256.digest(this.bytes)
85+
const p = sha256.digest(this.bytes)
86+
let bytes: Uint8Array
87+
88+
if (isPromise(p)) {
89+
({ bytes } = await p)
90+
} else {
91+
bytes = p.bytes
92+
}
8193

8294
return bytes
8395
}

packages/crypto/src/keys/rsa-class.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import forge from 'node-forge/lib/forge.js'
55
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
66
import 'node-forge/lib/sha512.js'
77
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8+
import { isPromise } from '../util.js'
89
import { exporter } from './exporter.js'
910
import * as pbm from './keys.js'
1011
import * as crypto from './rsa.js'
@@ -20,7 +21,7 @@ export class RsaPublicKey {
2021
this._key = key
2122
}
2223

23-
async verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): Promise<boolean> {
24+
verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): boolean | Promise<boolean> {
2425
return crypto.hashAndVerify(this._key, sig, data)
2526
}
2627

@@ -39,14 +40,18 @@ export class RsaPublicKey {
3940
return crypto.encrypt(this._key, bytes)
4041
}
4142

42-
equals (key: any): boolean {
43+
equals (key: any): boolean | boolean {
4344
return uint8ArrayEquals(this.bytes, key.bytes)
4445
}
4546

46-
async hash (): Promise<Uint8Array> {
47-
const { bytes } = await sha256.digest(this.bytes)
47+
hash (): Uint8Array | Promise<Uint8Array> {
48+
const p = sha256.digest(this.bytes)
49+
50+
if (isPromise(p)) {
51+
return p.then(({ bytes }) => bytes)
52+
}
4853

49-
return bytes
54+
return p.bytes
5055
}
5156
}
5257

@@ -63,7 +68,7 @@ export class RsaPrivateKey {
6368
return crypto.getRandomValues(16)
6469
}
6570

66-
async sign (message: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
71+
sign (message: Uint8Array | Uint8ArrayList): Uint8Array | Promise<Uint8Array> {
6772
return crypto.hashAndSign(this._key, message)
6873
}
6974

@@ -94,10 +99,14 @@ export class RsaPrivateKey {
9499
return uint8ArrayEquals(this.bytes, key.bytes)
95100
}
96101

97-
async hash (): Promise<Uint8Array> {
98-
const { bytes } = await sha256.digest(this.bytes)
102+
hash (): Uint8Array | Promise<Uint8Array> {
103+
const p = sha256.digest(this.bytes)
104+
105+
if (isPromise(p)) {
106+
return p.then(({ bytes }) => bytes)
107+
}
99108

100-
return bytes
109+
return p.bytes
101110
}
102111

103112
/**

packages/crypto/src/keys/secp256k1-browser.ts

+23-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CodeError } from '@libp2p/interface/errors'
22
import { secp256k1 as secp } from '@noble/curves/secp256k1'
33
import { sha256 } from 'multiformats/hashes/sha2'
4+
import { isPromise } from '../util.js'
45
import type { Uint8ArrayList } from 'uint8arraylist'
56

67
const PRIVATE_KEY_BYTE_LENGTH = 32
@@ -14,11 +15,18 @@ export function generateKey (): Uint8Array {
1415
/**
1516
* Hash and sign message with private key
1617
*/
17-
export async function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
18-
const { digest } = await sha256.digest(msg instanceof Uint8Array ? msg : msg.subarray())
18+
export function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): Uint8Array | Promise<Uint8Array> {
19+
const p = sha256.digest(msg instanceof Uint8Array ? msg : msg.subarray())
20+
21+
if (isPromise(p)) {
22+
return p.then(({ digest }) => secp.sign(digest, key).toDERRawBytes())
23+
.catch(err => {
24+
throw new CodeError(String(err), 'ERR_INVALID_INPUT')
25+
})
26+
}
27+
1928
try {
20-
const signature = secp.sign(digest, key)
21-
return signature.toDERRawBytes()
29+
return secp.sign(p.digest, key).toDERRawBytes()
2230
} catch (err) {
2331
throw new CodeError(String(err), 'ERR_INVALID_INPUT')
2432
}
@@ -27,10 +35,18 @@ export async function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8Array
2735
/**
2836
* Hash message and verify signature with public key
2937
*/
30-
export async function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array | Uint8ArrayList): Promise<boolean> {
38+
export function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array | Uint8ArrayList): boolean | Promise<boolean> {
39+
const p = sha256.digest(msg instanceof Uint8Array ? msg : msg.subarray())
40+
41+
if (isPromise(p)) {
42+
return p.then(({ digest }) => secp.verify(sig, digest, key))
43+
.catch(err => {
44+
throw new CodeError(String(err), 'ERR_INVALID_INPUT')
45+
})
46+
}
47+
3148
try {
32-
const { digest } = await sha256.digest(msg instanceof Uint8Array ? msg : msg.subarray())
33-
return secp.verify(sig, digest, key)
49+
return secp.verify(sig, p.digest, key)
3450
} catch (err) {
3551
throw new CodeError(String(err), 'ERR_INVALID_INPUT')
3652
}

packages/crypto/src/keys/secp256k1-class.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CodeError } from '@libp2p/interface/errors'
22
import { sha256 } from 'multiformats/hashes/sha2'
33
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
44
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5+
import { isPromise } from '../util.js'
56
import { exporter } from './exporter.js'
67
import * as keysProtobuf from './keys.js'
78
import * as crypto from './secp256k1.js'
@@ -16,7 +17,7 @@ export class Secp256k1PublicKey {
1617
this._key = key
1718
}
1819

19-
async verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): Promise<boolean> {
20+
verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): boolean {
2021
return crypto.hashAndVerify(this._key, sig, data)
2122
}
2223

@@ -36,7 +37,14 @@ export class Secp256k1PublicKey {
3637
}
3738

3839
async hash (): Promise<Uint8Array> {
39-
const { bytes } = await sha256.digest(this.bytes)
40+
const p = sha256.digest(this.bytes)
41+
let bytes: Uint8Array
42+
43+
if (isPromise(p)) {
44+
({ bytes } = await p)
45+
} else {
46+
bytes = p.bytes
47+
}
4048

4149
return bytes
4250
}
@@ -53,7 +61,7 @@ export class Secp256k1PrivateKey {
5361
crypto.validatePublicKey(this._publicKey)
5462
}
5563

56-
async sign (message: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
64+
sign (message: Uint8Array | Uint8ArrayList): Uint8Array | Promise<Uint8Array> {
5765
return crypto.hashAndSign(this._key, message)
5866
}
5967

@@ -76,10 +84,14 @@ export class Secp256k1PrivateKey {
7684
return uint8ArrayEquals(this.bytes, key.bytes)
7785
}
7886

79-
async hash (): Promise<Uint8Array> {
80-
const { bytes } = await sha256.digest(this.bytes)
87+
hash (): Uint8Array | Promise<Uint8Array> {
88+
const p = sha256.digest(this.bytes)
8189

82-
return bytes
90+
if (isPromise(p)) {
91+
return p.then(({ bytes }) => bytes)
92+
}
93+
94+
return p.bytes
8395
}
8496

8597
/**

packages/crypto/src/keys/secp256k1.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function generateKey (): Uint8Array {
1414
/**
1515
* Hash and sign message with private key
1616
*/
17-
export async function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
17+
export function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): Uint8Array {
1818
const hash = crypto.createHash('sha256')
1919

2020
if (msg instanceof Uint8Array) {
@@ -38,7 +38,7 @@ export async function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8Array
3838
/**
3939
* Hash message and verify signature with public key
4040
*/
41-
export async function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array | Uint8ArrayList): Promise<boolean> {
41+
export function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array | Uint8ArrayList): boolean {
4242
const hash = crypto.createHash('sha256')
4343

4444
if (msg instanceof Uint8Array) {

packages/crypto/src/util.ts

+10
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,13 @@ export function base64urlToBuffer (str: string, len?: number): Uint8Array {
4040

4141
return buf
4242
}
43+
44+
export function isPromise <T = unknown> (thing: any): thing is Promise<T> {
45+
if (thing == null) {
46+
return false
47+
}
48+
49+
return typeof thing.then === 'function' &&
50+
typeof thing.catch === 'function' &&
51+
typeof thing.finally === 'function'
52+
}

packages/crypto/test/helpers/test-garbage-error-handling.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
44

55
const garbage = [uint8ArrayFromString('00010203040506070809', 'base16'), {}, null, false, undefined, true, 1, 0, uint8ArrayFromString(''), 'aGVsbG93b3JsZA==', 'helloworld', '']
66

7-
export function testGarbage (fncName: string, fnc: (...args: Uint8Array[]) => Promise<any>, num?: number, skipBuffersAndStrings?: boolean): void {
7+
export function testGarbage (fncName: string, fnc: (...args: Uint8Array[]) => any | Promise<any>, num?: number, skipBuffersAndStrings?: boolean): void {
88
const count = num ?? 1
99

1010
garbage.forEach((garbage) => {

packages/crypto/test/keys/ed25519.spec.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ describe('ed25519', function () {
5757

5858
it('signs', async () => {
5959
const text = crypto.randomBytes(512)
60-
const sig = await key.sign(text)
61-
const res = await key.public.verify(text, sig)
60+
const sig = key.sign(text)
61+
const res = key.public.verify(text, sig)
6262
expect(res).to.be.eql(true)
6363
})
6464

@@ -67,15 +67,15 @@ describe('ed25519', function () {
6767
crypto.randomBytes(512),
6868
crypto.randomBytes(512)
6969
)
70-
const sig = await key.sign(text)
70+
const sig = key.sign(text)
7171

72-
await expect(key.sign(text.subarray()))
73-
.to.eventually.deep.equal(sig, 'list did not have same signature as a single buffer')
72+
expect(key.sign(text.subarray()))
73+
.to.deep.equal(sig, 'list did not have same signature as a single buffer')
7474

75-
await expect(key.public.verify(text, sig))
76-
.to.eventually.be.true('did not verify message as list')
77-
await expect(key.public.verify(text.subarray(), sig))
78-
.to.eventually.be.true('did not verify message as single buffer')
75+
expect(key.public.verify(text, sig))
76+
.to.be.true('did not verify message as list')
77+
expect(key.public.verify(text.subarray(), sig))
78+
.to.be.true('did not verify message as single buffer')
7979
})
8080

8181
it('encoding', () => {
@@ -178,8 +178,8 @@ describe('ed25519', function () {
178178

179179
it('sign and verify', async () => {
180180
const data = uint8ArrayFromString('hello world')
181-
const sig = await key.sign(data)
182-
const valid = await key.public.verify(data, sig)
181+
const sig = key.sign(data)
182+
const valid = key.public.verify(data, sig)
183183
expect(valid).to.eql(true)
184184
})
185185

@@ -194,8 +194,8 @@ describe('ed25519', function () {
194194

195195
it('fails to verify for different data', async () => {
196196
const data = uint8ArrayFromString('hello world')
197-
const sig = await key.sign(data)
198-
const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
197+
const sig = key.sign(data)
198+
const valid = key.public.verify(uint8ArrayFromString('hello'), sig)
199199
expect(valid).to.be.eql(false)
200200
})
201201

packages/crypto/test/keys/secp256k1.spec.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/await-thenable */ // secp is sync in node, async in browsers
12
/* eslint-env mocha */
23
import { expect } from 'aegir/chai'
34
import { Uint8ArrayList } from 'uint8arraylist'
@@ -48,13 +49,13 @@ describe('secp256k1 keys', () => {
4849
)
4950
const sig = await key.sign(text)
5051

51-
await expect(key.sign(text.subarray()))
52-
.to.eventually.deep.equal(sig, 'list did not have same signature as a single buffer')
52+
expect(await key.sign(text.subarray()))
53+
.to.deep.equal(sig, 'list did not have same signature as a single buffer')
5354

54-
await expect(key.public.verify(text, sig))
55-
.to.eventually.be.true('did not verify message as list')
56-
await expect(key.public.verify(text.subarray(), sig))
57-
.to.eventually.be.true('did not verify message as single buffer')
55+
expect(await key.public.verify(text, sig))
56+
.to.be.true('did not verify message as list')
57+
expect(await key.public.verify(text.subarray(), sig))
58+
.to.be.true('did not verify message as single buffer')
5859
})
5960

6061
it('encoding', () => {
@@ -169,19 +170,25 @@ describe('crypto functions', () => {
169170
})
170171

171172
it('errors if given a null Uint8Array to sign', async () => {
172-
// @ts-expect-error incorrect args
173-
await expect(secp256k1Crypto.hashAndSign(privKey, null)).to.eventually.be.rejected()
173+
await expect((async () => {
174+
// @ts-expect-error incorrect args
175+
await secp256k1Crypto.hashAndSign(privKey, null)
176+
})()).to.eventually.be.rejected()
174177
})
175178

176179
it('errors when signing with an invalid key', async () => {
177-
await expect(secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello'))).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_INPUT')
180+
await expect((async () => {
181+
await secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello'))
182+
})()).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_INPUT')
178183
})
179184

180185
it('errors if given a null Uint8Array to validate', async () => {
181186
const sig = await secp256k1Crypto.hashAndSign(privKey, uint8ArrayFromString('hello'))
182187

183-
// @ts-expect-error incorrect args
184-
await expect(secp256k1Crypto.hashAndVerify(privKey, sig, null)).to.eventually.be.rejected()
188+
await expect((async () => {
189+
// @ts-expect-error incorrect args
190+
await secp256k1Crypto.hashAndVerify(privKey, sig, null)
191+
})()).to.eventually.be.rejected()
185192
})
186193

187194
it('throws when compressing an invalid public key', () => {

0 commit comments

Comments
 (0)