@libp2p/crypto 5.0.15 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/index.min.js +1 -1
  2. package/dist/src/keys/ecdsa/ecdsa.d.ts +27 -0
  3. package/dist/src/keys/ecdsa/ecdsa.d.ts.map +1 -0
  4. package/dist/src/keys/ecdsa/ecdsa.js +72 -0
  5. package/dist/src/keys/ecdsa/ecdsa.js.map +1 -0
  6. package/dist/src/keys/ecdsa/index.d.ts +10 -0
  7. package/dist/src/keys/ecdsa/index.d.ts.map +1 -0
  8. package/dist/src/keys/ecdsa/index.js +39 -0
  9. package/dist/src/keys/ecdsa/index.js.map +1 -0
  10. package/dist/src/keys/ecdsa/utils.d.ts +11 -0
  11. package/dist/src/keys/ecdsa/utils.d.ts.map +1 -0
  12. package/dist/src/keys/ecdsa/utils.js +165 -0
  13. package/dist/src/keys/ecdsa/utils.js.map +1 -0
  14. package/dist/src/keys/index.d.ts +13 -3
  15. package/dist/src/keys/index.d.ts.map +1 -1
  16. package/dist/src/keys/index.js +101 -11
  17. package/dist/src/keys/index.js.map +1 -1
  18. package/dist/src/keys/keys.d.ts +2 -1
  19. package/dist/src/keys/keys.d.ts.map +1 -1
  20. package/dist/src/keys/keys.js +2 -0
  21. package/dist/src/keys/keys.js.map +1 -1
  22. package/dist/src/keys/rsa/der.d.ts +2 -1
  23. package/dist/src/keys/rsa/der.d.ts.map +1 -1
  24. package/dist/src/keys/rsa/der.js +53 -10
  25. package/dist/src/keys/rsa/der.js.map +1 -1
  26. package/dist/src/keys/rsa/index.browser.d.ts +1 -0
  27. package/dist/src/keys/rsa/index.browser.d.ts.map +1 -1
  28. package/dist/src/keys/rsa/index.browser.js +1 -0
  29. package/dist/src/keys/rsa/index.browser.js.map +1 -1
  30. package/dist/src/keys/rsa/index.d.ts +1 -0
  31. package/dist/src/keys/rsa/index.d.ts.map +1 -1
  32. package/dist/src/keys/rsa/index.js +1 -0
  33. package/dist/src/keys/rsa/index.js.map +1 -1
  34. package/dist/src/keys/rsa/rsa.d.ts +4 -4
  35. package/dist/src/keys/rsa/rsa.d.ts.map +1 -1
  36. package/dist/src/keys/rsa/rsa.js +10 -10
  37. package/dist/src/keys/rsa/rsa.js.map +1 -1
  38. package/dist/src/keys/rsa/utils.d.ts +12 -2
  39. package/dist/src/keys/rsa/utils.d.ts.map +1 -1
  40. package/dist/src/keys/rsa/utils.js +41 -16
  41. package/dist/src/keys/rsa/utils.js.map +1 -1
  42. package/dist/src/keys/secp256k1/index.browser.d.ts +4 -0
  43. package/dist/src/keys/secp256k1/index.browser.d.ts.map +1 -1
  44. package/dist/src/keys/secp256k1/index.browser.js +4 -0
  45. package/dist/src/keys/secp256k1/index.browser.js.map +1 -1
  46. package/dist/src/keys/secp256k1/index.d.ts +4 -0
  47. package/dist/src/keys/secp256k1/index.d.ts.map +1 -1
  48. package/dist/src/keys/secp256k1/index.js +4 -0
  49. package/dist/src/keys/secp256k1/index.js.map +1 -1
  50. package/dist/typedoc-urls.json +4 -0
  51. package/package.json +2 -2
  52. package/src/keys/ecdsa/ecdsa.ts +91 -0
  53. package/src/keys/ecdsa/index.ts +50 -0
  54. package/src/keys/ecdsa/utils.ts +212 -0
  55. package/src/keys/index.ts +132 -15
  56. package/src/keys/keys.proto +1 -0
  57. package/src/keys/keys.ts +4 -2
  58. package/src/keys/rsa/der.ts +68 -11
  59. package/src/keys/rsa/index.browser.ts +1 -0
  60. package/src/keys/rsa/index.ts +2 -0
  61. package/src/keys/rsa/rsa.ts +10 -10
  62. package/src/keys/rsa/utils.ts +48 -16
  63. package/src/keys/secp256k1/index.browser.ts +6 -0
  64. package/src/keys/secp256k1/index.ts +6 -0
@@ -16,10 +16,14 @@
16
16
  "generateKeyPairFromSeed": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPairFromSeed.html",
17
17
  "./keys:generateKeyPairFromSeed": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPairFromSeed.html",
18
18
  "keyStretcher": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.keyStretcher.html",
19
+ "privateKeyFromCryptoKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromCryptoKeyPair.html",
20
+ "./keys:privateKeyFromCryptoKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromCryptoKeyPair.html",
19
21
  "privateKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromProtobuf.html",
20
22
  "./keys:privateKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromProtobuf.html",
21
23
  "privateKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromRaw.html",
22
24
  "./keys:privateKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromRaw.html",
25
+ "privateKeyToCryptoKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToCryptoKeyPair.html",
26
+ "./keys:privateKeyToCryptoKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToCryptoKeyPair.html",
23
27
  "privateKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToProtobuf.html",
24
28
  "./keys:privateKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToProtobuf.html",
25
29
  "publicKeyFromMultihash": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromMultihash.html",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/crypto",
3
- "version": "5.0.15",
3
+ "version": "5.1.0",
4
4
  "description": "Crypto primitives for libp2p",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/crypto#readme",
@@ -92,7 +92,7 @@
92
92
  "generate": "protons ./src/keys/keys.proto"
93
93
  },
94
94
  "dependencies": {
95
- "@libp2p/interface": "^2.7.0",
95
+ "@libp2p/interface": "^2.8.0",
96
96
  "@noble/curves": "^1.7.0",
97
97
  "@noble/hashes": "^1.6.1",
98
98
  "multiformats": "^13.3.1",
@@ -0,0 +1,91 @@
1
+ import { base58btc } from 'multiformats/bases/base58'
2
+ import { CID } from 'multiformats/cid'
3
+ import { identity } from 'multiformats/hashes/identity'
4
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
5
+ import { publicKeyToProtobuf } from '../index.js'
6
+ import { privateKeyToPKIMessage, publicKeyToPKIMessage } from './utils.js'
7
+ import { hashAndVerify, hashAndSign } from './index.js'
8
+ import type { ECDSAPublicKey as ECDSAPublicKeyInterface, ECDSAPrivateKey as ECDSAPrivateKeyInterface } from '@libp2p/interface'
9
+ import type { Digest } from 'multiformats/hashes/digest'
10
+ import type { Uint8ArrayList } from 'uint8arraylist'
11
+
12
+ export class ECDSAPublicKey implements ECDSAPublicKeyInterface {
13
+ public readonly type = 'ECDSA'
14
+ public readonly jwk: JsonWebKey
15
+ private _raw?: Uint8Array
16
+
17
+ constructor (jwk: JsonWebKey) {
18
+ this.jwk = jwk
19
+ }
20
+
21
+ get raw (): Uint8Array {
22
+ if (this._raw == null) {
23
+ this._raw = publicKeyToPKIMessage(this.jwk)
24
+ }
25
+
26
+ return this._raw
27
+ }
28
+
29
+ toMultihash (): Digest<0x0, number> {
30
+ return identity.digest(publicKeyToProtobuf(this))
31
+ }
32
+
33
+ toCID (): CID<unknown, 114, 0x0, 1> {
34
+ return CID.createV1(114, this.toMultihash())
35
+ }
36
+
37
+ toString (): string {
38
+ return base58btc.encode(this.toMultihash().bytes).substring(1)
39
+ }
40
+
41
+ equals (key?: any): boolean {
42
+ if (key == null || !(key.raw instanceof Uint8Array)) {
43
+ return false
44
+ }
45
+
46
+ return uint8ArrayEquals(this.raw, key.raw)
47
+ }
48
+
49
+ async verify (data: Uint8Array | Uint8ArrayList, sig: Uint8Array): Promise<boolean> {
50
+ return hashAndVerify(this.jwk, sig, data)
51
+ }
52
+ }
53
+
54
+ export class ECDSAPrivateKey implements ECDSAPrivateKeyInterface {
55
+ public readonly type = 'ECDSA'
56
+ public readonly jwk: JsonWebKey
57
+ public readonly publicKey: ECDSAPublicKey
58
+ private _raw?: Uint8Array
59
+
60
+ constructor (jwk: JsonWebKey) {
61
+ this.jwk = jwk
62
+ this.publicKey = new ECDSAPublicKey({
63
+ crv: jwk.crv,
64
+ ext: jwk.ext,
65
+ key_ops: ['verify'],
66
+ kty: 'EC',
67
+ x: jwk.x,
68
+ y: jwk.y
69
+ })
70
+ }
71
+
72
+ get raw (): Uint8Array {
73
+ if (this._raw == null) {
74
+ this._raw = privateKeyToPKIMessage(this.jwk)
75
+ }
76
+
77
+ return this._raw
78
+ }
79
+
80
+ equals (key?: any): boolean {
81
+ if (key == null || !(key.raw instanceof Uint8Array)) {
82
+ return false
83
+ }
84
+
85
+ return uint8ArrayEquals(this.raw, key.raw)
86
+ }
87
+
88
+ async sign (message: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
89
+ return hashAndSign(this.jwk, message)
90
+ }
91
+ }
@@ -0,0 +1,50 @@
1
+ import type { JWKKeyPair } from '../interface.js'
2
+ import type { Uint8ArrayList } from 'uint8arraylist'
3
+
4
+ export type Curve = 'P-256' | 'P-384' | 'P-521'
5
+
6
+ export const ECDSA_P_256_OID = '1.2.840.10045.3.1.7'
7
+ export const ECDSA_P_384_OID = '1.3.132.0.34'
8
+ export const ECDSA_P_521_OID = '1.3.132.0.35'
9
+
10
+ export async function generateECDSAKey (curve: Curve = 'P-256'): Promise<JWKKeyPair> {
11
+ const keyPair = await crypto.subtle.generateKey({
12
+ name: 'ECDSA',
13
+ namedCurve: curve
14
+ }, true, ['sign', 'verify'])
15
+
16
+ return {
17
+ publicKey: await crypto.subtle.exportKey('jwk', keyPair.publicKey),
18
+ privateKey: await crypto.subtle.exportKey('jwk', keyPair.privateKey)
19
+ }
20
+ }
21
+
22
+ export async function hashAndSign (key: JsonWebKey, msg: Uint8Array | Uint8ArrayList): Promise<Uint8Array> {
23
+ const privateKey = await crypto.subtle.importKey('jwk', key, {
24
+ name: 'ECDSA',
25
+ namedCurve: key.crv ?? 'P-256'
26
+ }, false, ['sign'])
27
+
28
+ const signature = await crypto.subtle.sign({
29
+ name: 'ECDSA',
30
+ hash: {
31
+ name: 'SHA-256'
32
+ }
33
+ }, privateKey, msg.subarray())
34
+
35
+ return new Uint8Array(signature, 0, signature.byteLength)
36
+ }
37
+
38
+ export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint8Array | Uint8ArrayList): Promise<boolean> {
39
+ const publicKey = await crypto.subtle.importKey('jwk', key, {
40
+ name: 'ECDSA',
41
+ namedCurve: key.crv ?? 'P-256'
42
+ }, false, ['verify'])
43
+
44
+ return crypto.subtle.verify({
45
+ name: 'ECDSA',
46
+ hash: {
47
+ name: 'SHA-256'
48
+ }
49
+ }, publicKey, sig, msg.subarray())
50
+ }
@@ -0,0 +1,212 @@
1
+ import { InvalidParametersError } from '@libp2p/interface'
2
+ import { Uint8ArrayList } from 'uint8arraylist'
3
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5
+ import { decodeDer, encodeBitString, encodeInteger, encodeOctetString, encodeSequence } from '../rsa/der.js'
6
+ import { ECDSAPrivateKey as ECDSAPrivateKeyClass, ECDSAPublicKey as ECDSAPublicKeyClass } from './ecdsa.js'
7
+ import { generateECDSAKey } from './index.js'
8
+ import type { Curve } from '../ecdh/index.js'
9
+ import type { ECDSAPublicKey, ECDSAPrivateKey } from '@libp2p/interface'
10
+
11
+ // 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve)
12
+ const OID_256 = Uint8Array.from([0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07])
13
+ // 1.3.132.0.34 secp384r1 (SECG (Certicom) named elliptic curve)
14
+ const OID_384 = Uint8Array.from([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22])
15
+ // 1.3.132.0.35 secp521r1 (SECG (Certicom) named elliptic curve)
16
+ const OID_521 = Uint8Array.from([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23])
17
+
18
+ const P_256_KEY_JWK = {
19
+ ext: true,
20
+ kty: 'EC',
21
+ crv: 'P-256'
22
+ }
23
+
24
+ const P_384_KEY_JWK = {
25
+ ext: true,
26
+ kty: 'EC',
27
+ crv: 'P-384'
28
+ }
29
+
30
+ const P_521_KEY_JWK = {
31
+ ext: true,
32
+ kty: 'EC',
33
+ crv: 'P-521'
34
+ }
35
+
36
+ const P_256_KEY_LENGTH = 32
37
+ const P_384_KEY_LENGTH = 48
38
+ const P_521_KEY_LENGTH = 66
39
+
40
+ export function unmarshalECDSAPrivateKey (bytes: Uint8Array): ECDSAPrivateKey {
41
+ const message = decodeDer(bytes)
42
+
43
+ return pkiMessageToECDSAPrivateKey(message)
44
+ }
45
+
46
+ export function pkiMessageToECDSAPrivateKey (message: any): ECDSAPrivateKey {
47
+ const privateKey = message[1]
48
+ const d = uint8ArrayToString(privateKey, 'base64url')
49
+ const coordinates: Uint8Array = message[2][1][0]
50
+ const offset = 1
51
+ let x: string
52
+ let y: string
53
+
54
+ if (privateKey.byteLength === P_256_KEY_LENGTH) {
55
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_256_KEY_LENGTH), 'base64url')
56
+ y = uint8ArrayToString(coordinates.subarray(offset + P_256_KEY_LENGTH), 'base64url')
57
+
58
+ return new ECDSAPrivateKeyClass({
59
+ ...P_256_KEY_JWK,
60
+ key_ops: ['sign'],
61
+ d,
62
+ x,
63
+ y
64
+ })
65
+ }
66
+
67
+ if (privateKey.byteLength === P_384_KEY_LENGTH) {
68
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_384_KEY_LENGTH), 'base64url')
69
+ y = uint8ArrayToString(coordinates.subarray(offset + P_384_KEY_LENGTH), 'base64url')
70
+
71
+ return new ECDSAPrivateKeyClass({
72
+ ...P_384_KEY_JWK,
73
+ key_ops: ['sign'],
74
+ d,
75
+ x,
76
+ y
77
+ })
78
+ }
79
+
80
+ if (privateKey.byteLength === P_521_KEY_LENGTH) {
81
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_521_KEY_LENGTH), 'base64url')
82
+ y = uint8ArrayToString(coordinates.subarray(offset + P_521_KEY_LENGTH), 'base64url')
83
+
84
+ return new ECDSAPrivateKeyClass({
85
+ ...P_521_KEY_JWK,
86
+ key_ops: ['sign'],
87
+ d,
88
+ x,
89
+ y
90
+ })
91
+ }
92
+
93
+ throw new InvalidParametersError(`Private key length was wrong length, got ${privateKey.byteLength}, expected 32, 48 or 66`)
94
+ }
95
+
96
+ export function unmarshalECDSAPublicKey (bytes: Uint8Array): ECDSAPublicKey {
97
+ const message = decodeDer(bytes)
98
+
99
+ return pkiMessageToECDSAPublicKey(message)
100
+ }
101
+
102
+ export function pkiMessageToECDSAPublicKey (message: any): ECDSAPublicKey {
103
+ const coordinates = message[1][1][0]
104
+ const offset = 1
105
+ let x: string
106
+ let y: string
107
+
108
+ if (coordinates.byteLength === ((P_256_KEY_LENGTH * 2) + 1)) {
109
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_256_KEY_LENGTH), 'base64url')
110
+ y = uint8ArrayToString(coordinates.subarray(offset + P_256_KEY_LENGTH), 'base64url')
111
+
112
+ return new ECDSAPublicKeyClass({
113
+ ...P_256_KEY_JWK,
114
+ key_ops: ['verify'],
115
+ x,
116
+ y
117
+ })
118
+ }
119
+
120
+ if (coordinates.byteLength === ((P_384_KEY_LENGTH * 2) + 1)) {
121
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_384_KEY_LENGTH), 'base64url')
122
+ y = uint8ArrayToString(coordinates.subarray(offset + P_384_KEY_LENGTH), 'base64url')
123
+
124
+ return new ECDSAPublicKeyClass({
125
+ ...P_384_KEY_JWK,
126
+ key_ops: ['verify'],
127
+ x,
128
+ y
129
+ })
130
+ }
131
+
132
+ if (coordinates.byteLength === ((P_521_KEY_LENGTH * 2) + 1)) {
133
+ x = uint8ArrayToString(coordinates.subarray(offset, offset + P_521_KEY_LENGTH), 'base64url')
134
+ y = uint8ArrayToString(coordinates.subarray(offset + P_521_KEY_LENGTH), 'base64url')
135
+
136
+ return new ECDSAPublicKeyClass({
137
+ ...P_521_KEY_JWK,
138
+ key_ops: ['verify'],
139
+ x,
140
+ y
141
+ })
142
+ }
143
+
144
+ throw new InvalidParametersError(`coordinates were wrong length, got ${coordinates.byteLength}, expected 65, 97 or 133`)
145
+ }
146
+
147
+ export function privateKeyToPKIMessage (privateKey: JsonWebKey): Uint8Array {
148
+ return encodeSequence([
149
+ encodeInteger(Uint8Array.from([1])), // header
150
+ encodeOctetString(uint8ArrayFromString(privateKey.d ?? '', 'base64url')), // body
151
+ encodeSequence([ // PKIProtection
152
+ getOID(privateKey.crv)
153
+ ], 0xA0),
154
+ encodeSequence([ // extraCerts
155
+ encodeBitString(
156
+ new Uint8ArrayList(
157
+ Uint8Array.from([0x04]),
158
+ uint8ArrayFromString(privateKey.x ?? '', 'base64url'),
159
+ uint8ArrayFromString(privateKey.y ?? '', 'base64url')
160
+ )
161
+ )
162
+ ], 0xA1)
163
+ ]).subarray()
164
+ }
165
+
166
+ export function publicKeyToPKIMessage (publicKey: JsonWebKey): Uint8Array {
167
+ return encodeSequence([
168
+ encodeInteger(Uint8Array.from([1])), // header
169
+ encodeSequence([ // PKIProtection
170
+ getOID(publicKey.crv)
171
+ ], 0xA0),
172
+ encodeSequence([ // extraCerts
173
+ encodeBitString(
174
+ new Uint8ArrayList(
175
+ Uint8Array.from([0x04]),
176
+ uint8ArrayFromString(publicKey.x ?? '', 'base64url'),
177
+ uint8ArrayFromString(publicKey.y ?? '', 'base64url')
178
+ )
179
+ )
180
+ ], 0xA1)
181
+ ]).subarray()
182
+ }
183
+
184
+ function getOID (curve?: string): Uint8Array {
185
+ if (curve === 'P-256') {
186
+ return OID_256
187
+ }
188
+
189
+ if (curve === 'P-384') {
190
+ return OID_384
191
+ }
192
+
193
+ if (curve === 'P-521') {
194
+ return OID_521
195
+ }
196
+
197
+ throw new InvalidParametersError(`Invalid curve ${curve}`)
198
+ }
199
+
200
+ export async function generateECDSAKeyPair (curve: Curve = 'P-256'): Promise<ECDSAPrivateKey> {
201
+ const key = await generateECDSAKey(curve)
202
+
203
+ return new ECDSAPrivateKeyClass(key.privateKey)
204
+ }
205
+
206
+ export function ensureECDSAKey (key: Uint8Array, length: number): Uint8Array {
207
+ key = Uint8Array.from(key ?? [])
208
+ if (key.length !== length) {
209
+ throw new InvalidParametersError(`Key must be a Uint8Array of length ${length}, got ${key.length}`)
210
+ }
211
+ return key
212
+ }
package/src/keys/index.ts CHANGED
@@ -8,12 +8,20 @@
8
8
  * For encryption / decryption support, RSA keys should be used.
9
9
  */
10
10
 
11
- import { UnsupportedKeyTypeError } from '@libp2p/interface'
11
+ import { InvalidParametersError, UnsupportedKeyTypeError } from '@libp2p/interface'
12
+ import { ECDSAPrivateKey as ECDSAPrivateKeyClass } from './ecdsa/ecdsa.js'
13
+ import { ECDSA_P_256_OID, ECDSA_P_384_OID, ECDSA_P_521_OID } from './ecdsa/index.js'
14
+ import { generateECDSAKeyPair, pkiMessageToECDSAPrivateKey, pkiMessageToECDSAPublicKey, unmarshalECDSAPrivateKey, unmarshalECDSAPublicKey } from './ecdsa/utils.js'
15
+ import { privateKeyLength as ed25519PrivateKeyLength, publicKeyLength as ed25519PublicKeyLength } from './ed25519/index.js'
12
16
  import { generateEd25519KeyPair, generateEd25519KeyPairFromSeed, unmarshalEd25519PrivateKey, unmarshalEd25519PublicKey } from './ed25519/utils.js'
13
17
  import * as pb from './keys.js'
14
- import { pkcs1ToRSAPrivateKey, pkixToRSAPublicKey, generateRSAKeyPair } from './rsa/utils.js'
18
+ import { decodeDer } from './rsa/der.js'
19
+ import { RSAES_PKCS1_V1_5_OID } from './rsa/index.js'
20
+ import { pkcs1ToRSAPrivateKey, pkixToRSAPublicKey, generateRSAKeyPair, pkcs1MessageToRSAPrivateKey, pkixMessageToRSAPublicKey, jwkToRSAPrivateKey } from './rsa/utils.js'
21
+ import { privateKeyLength as secp256k1PrivateKeyLength, publicKeyLength as secp256k1PublicKeyLength } from './secp256k1/index.js'
15
22
  import { generateSecp256k1KeyPair, unmarshalSecp256k1PrivateKey, unmarshalSecp256k1PublicKey } from './secp256k1/utils.js'
16
- import type { PrivateKey, PublicKey, KeyType, RSAPrivateKey, Secp256k1PrivateKey, Ed25519PrivateKey, Secp256k1PublicKey, Ed25519PublicKey } from '@libp2p/interface'
23
+ import type { Curve } from './ecdsa/index.js'
24
+ import type { PrivateKey, PublicKey, KeyType, RSAPrivateKey, Secp256k1PrivateKey, Ed25519PrivateKey, Secp256k1PublicKey, Ed25519PublicKey, ECDSAPrivateKey, ECDSAPublicKey } from '@libp2p/interface'
17
25
  import type { MultihashDigest } from 'multiformats'
18
26
  import type { Digest } from 'multiformats/hashes/digest'
19
27
 
@@ -27,9 +35,10 @@ export { keyStretcher } from './key-stretcher.js'
27
35
  */
28
36
  export async function generateKeyPair (type: 'Ed25519'): Promise<Ed25519PrivateKey>
29
37
  export async function generateKeyPair (type: 'secp256k1'): Promise<Secp256k1PrivateKey>
38
+ export async function generateKeyPair (type: 'ECDSA', curve?: Curve): Promise<ECDSAPrivateKey>
30
39
  export async function generateKeyPair (type: 'RSA', bits?: number): Promise<RSAPrivateKey>
31
40
  export async function generateKeyPair (type: KeyType, bits?: number): Promise<PrivateKey>
32
- export async function generateKeyPair (type: KeyType, bits?: number): Promise<unknown> {
41
+ export async function generateKeyPair (type: KeyType, bits?: number | string): Promise<unknown> {
33
42
  if (type === 'Ed25519') {
34
43
  return generateEd25519KeyPair()
35
44
  }
@@ -39,7 +48,11 @@ export async function generateKeyPair (type: KeyType, bits?: number): Promise<un
39
48
  }
40
49
 
41
50
  if (type === 'RSA') {
42
- return generateRSAKeyPair(bits ?? 2048)
51
+ return generateRSAKeyPair(toBits(bits))
52
+ }
53
+
54
+ if (type === 'ECDSA') {
55
+ return generateECDSAKeyPair(toCurve(bits))
43
56
  }
44
57
 
45
58
  throw new UnsupportedKeyTypeError()
@@ -81,6 +94,8 @@ export function publicKeyFromProtobuf (buf: Uint8Array, digest?: Digest<18, numb
81
94
  return unmarshalEd25519PublicKey(data)
82
95
  case pb.KeyType.secp256k1:
83
96
  return unmarshalSecp256k1PublicKey(data)
97
+ case pb.KeyType.ECDSA:
98
+ return unmarshalECDSAPublicKey(data)
84
99
  default:
85
100
  throw new UnsupportedKeyTypeError()
86
101
  }
@@ -90,13 +105,24 @@ export function publicKeyFromProtobuf (buf: Uint8Array, digest?: Digest<18, numb
90
105
  * Creates a public key from the raw key bytes
91
106
  */
92
107
  export function publicKeyFromRaw (buf: Uint8Array): PublicKey {
93
- if (buf.byteLength === 32) {
108
+ if (buf.byteLength === ed25519PublicKeyLength) {
94
109
  return unmarshalEd25519PublicKey(buf)
95
- } else if (buf.byteLength === 33) {
110
+ } else if (buf.byteLength === secp256k1PublicKeyLength) {
96
111
  return unmarshalSecp256k1PublicKey(buf)
97
- } else {
98
- return pkixToRSAPublicKey(buf)
99
112
  }
113
+
114
+ const message = decodeDer(buf)
115
+ const ecdsaOid = message[1]?.[0]
116
+
117
+ if (ecdsaOid === ECDSA_P_256_OID || ecdsaOid === ECDSA_P_384_OID || ecdsaOid === ECDSA_P_521_OID) {
118
+ return pkiMessageToECDSAPublicKey(message)
119
+ }
120
+
121
+ if (message[0]?.[0] === RSAES_PKCS1_V1_5_OID) {
122
+ return pkixMessageToRSAPublicKey(message, buf)
123
+ }
124
+
125
+ throw new InvalidParametersError('Could not extract public key from raw bytes')
100
126
  }
101
127
 
102
128
  /**
@@ -106,7 +132,7 @@ export function publicKeyFromRaw (buf: Uint8Array): PublicKey {
106
132
  * RSA keys are not supported as in practice we they are not stored in identity
107
133
  * multihash since the hash would be very large.
108
134
  */
109
- export function publicKeyFromMultihash (digest: MultihashDigest<0x0>): Ed25519PublicKey | Secp256k1PublicKey {
135
+ export function publicKeyFromMultihash (digest: MultihashDigest<0x0>): Ed25519PublicKey | Secp256k1PublicKey | ECDSAPublicKey {
110
136
  const { Type, Data } = pb.PublicKey.decode(digest.digest)
111
137
  const data = Data ?? new Uint8Array()
112
138
 
@@ -115,6 +141,8 @@ export function publicKeyFromMultihash (digest: MultihashDigest<0x0>): Ed25519Pu
115
141
  return unmarshalEd25519PublicKey(data)
116
142
  case pb.KeyType.secp256k1:
117
143
  return unmarshalSecp256k1PublicKey(data)
144
+ case pb.KeyType.ECDSA:
145
+ return unmarshalECDSAPublicKey(data)
118
146
  default:
119
147
  throw new UnsupportedKeyTypeError()
120
148
  }
@@ -133,7 +161,7 @@ export function publicKeyToProtobuf (key: PublicKey): Uint8Array {
133
161
  /**
134
162
  * Converts a protobuf serialized private key into its representative object
135
163
  */
136
- export function privateKeyFromProtobuf (buf: Uint8Array): Ed25519PrivateKey | Secp256k1PrivateKey | RSAPrivateKey {
164
+ export function privateKeyFromProtobuf (buf: Uint8Array): Ed25519PrivateKey | Secp256k1PrivateKey | RSAPrivateKey | ECDSAPrivateKey {
137
165
  const decoded = pb.PrivateKey.decode(buf)
138
166
  const data = decoded.Data ?? new Uint8Array()
139
167
 
@@ -144,6 +172,8 @@ export function privateKeyFromProtobuf (buf: Uint8Array): Ed25519PrivateKey | Se
144
172
  return unmarshalEd25519PrivateKey(data)
145
173
  case pb.KeyType.secp256k1:
146
174
  return unmarshalSecp256k1PrivateKey(data)
175
+ case pb.KeyType.ECDSA:
176
+ return unmarshalECDSAPrivateKey(data)
147
177
  default:
148
178
  throw new UnsupportedKeyTypeError()
149
179
  }
@@ -155,13 +185,24 @@ export function privateKeyFromProtobuf (buf: Uint8Array): Ed25519PrivateKey | Se
155
185
  * differentiate between Ed25519 and secp256k1 keys as they are the same length.
156
186
  */
157
187
  export function privateKeyFromRaw (buf: Uint8Array): PrivateKey {
158
- if (buf.byteLength === 64) {
188
+ if (buf.byteLength === ed25519PrivateKeyLength) {
159
189
  return unmarshalEd25519PrivateKey(buf)
160
- } else if (buf.byteLength === 32) {
190
+ } else if (buf.byteLength === secp256k1PrivateKeyLength) {
161
191
  return unmarshalSecp256k1PrivateKey(buf)
162
- } else {
163
- return pkcs1ToRSAPrivateKey(buf)
164
192
  }
193
+
194
+ const message = decodeDer(buf)
195
+ const ecdsaOid = message[2]?.[0]
196
+
197
+ if (ecdsaOid === ECDSA_P_256_OID || ecdsaOid === ECDSA_P_384_OID || ecdsaOid === ECDSA_P_521_OID) {
198
+ return pkiMessageToECDSAPrivateKey(message)
199
+ }
200
+
201
+ if (message.length > 8) {
202
+ return pkcs1MessageToRSAPrivateKey(message)
203
+ }
204
+
205
+ throw new InvalidParametersError('Could not extract private key from raw bytes')
165
206
  }
166
207
 
167
208
  /**
@@ -173,3 +214,79 @@ export function privateKeyToProtobuf (key: PrivateKey): Uint8Array {
173
214
  Data: key.raw
174
215
  })
175
216
  }
217
+
218
+ function toBits (bits: any): number {
219
+ if (bits == null) {
220
+ return 2048
221
+ }
222
+
223
+ return parseInt(bits, 10)
224
+ }
225
+
226
+ function toCurve (curve: any): Curve {
227
+ if (curve === 'P-256' || curve == null) {
228
+ return 'P-256'
229
+ }
230
+
231
+ if (curve === 'P-384') {
232
+ return 'P-384'
233
+ }
234
+
235
+ if (curve === 'P-521') {
236
+ return 'P-521'
237
+ }
238
+
239
+ throw new InvalidParametersError('Unsupported curve, should be P-256, P-384 or P-521')
240
+ }
241
+
242
+ /**
243
+ * Convert a libp2p RSA or ECDSA private key to a WebCrypto CryptoKeyPair
244
+ */
245
+ export async function privateKeyToCryptoKeyPair (privateKey: PrivateKey): Promise<CryptoKeyPair> {
246
+ if (privateKey.type === 'RSA') {
247
+ return {
248
+ privateKey: await crypto.subtle.importKey('jwk', privateKey.jwk, {
249
+ name: 'RSASSA-PKCS1-v1_5',
250
+ hash: { name: 'SHA-256' }
251
+ }, true, ['sign']),
252
+ publicKey: await crypto.subtle.importKey('jwk', privateKey.publicKey.jwk, {
253
+ name: 'RSASSA-PKCS1-v1_5',
254
+ hash: { name: 'SHA-256' }
255
+ }, true, ['verify'])
256
+ }
257
+ }
258
+
259
+ if (privateKey.type === 'ECDSA') {
260
+ return {
261
+ privateKey: await crypto.subtle.importKey('jwk', privateKey.jwk, {
262
+ name: 'ECDSA',
263
+ namedCurve: privateKey.jwk.crv ?? 'P-256'
264
+ }, true, ['sign']),
265
+ publicKey: await crypto.subtle.importKey('jwk', privateKey.publicKey.jwk, {
266
+ name: 'ECDSA',
267
+ namedCurve: privateKey.publicKey.jwk.crv ?? 'P-256'
268
+ }, true, ['verify'])
269
+ }
270
+ }
271
+
272
+ throw new InvalidParametersError('Only RSA and ECDSA keys are supported')
273
+ }
274
+
275
+ /**
276
+ * Convert a RSA or ECDSA WebCrypto CryptoKeyPair to a libp2p private key
277
+ */
278
+ export async function privateKeyFromCryptoKeyPair (keyPair: CryptoKeyPair): Promise<PrivateKey> {
279
+ if (keyPair.privateKey.algorithm.name === 'RSASSA-PKCS1-v1_5') {
280
+ const jwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey)
281
+
282
+ return jwkToRSAPrivateKey(jwk)
283
+ }
284
+
285
+ if (keyPair.privateKey.algorithm.name === 'ECDSA') {
286
+ const jwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey)
287
+
288
+ return new ECDSAPrivateKeyClass(jwk)
289
+ }
290
+
291
+ throw new InvalidParametersError('Only RSA and ECDSA keys are supported')
292
+ }
@@ -4,6 +4,7 @@ enum KeyType {
4
4
  RSA = 0;
5
5
  Ed25519 = 1;
6
6
  secp256k1 = 2;
7
+ ECDSA = 3;
7
8
  }
8
9
  message PublicKey {
9
10
  // the proto2 version of this field is "required" which means it will have
package/src/keys/keys.ts CHANGED
@@ -10,13 +10,15 @@ import type { Uint8ArrayList } from 'uint8arraylist'
10
10
  export enum KeyType {
11
11
  RSA = 'RSA',
12
12
  Ed25519 = 'Ed25519',
13
- secp256k1 = 'secp256k1'
13
+ secp256k1 = 'secp256k1',
14
+ ECDSA = 'ECDSA'
14
15
  }
15
16
 
16
17
  enum __KeyTypeValues {
17
18
  RSA = 0,
18
19
  Ed25519 = 1,
19
- secp256k1 = 2
20
+ secp256k1 = 2,
21
+ ECDSA = 3
20
22
  }
21
23
 
22
24
  export namespace KeyType {