UNPKG

14.2 kBPlain TextView Raw
1import { concat, fromString, toString } from 'uint8arrays'
2import { x25519 } from '@noble/curves/ed25519'
3import type { EphemeralKeyPair } from './encryption/types.js'
4import { varint } from 'multiformats'
5import { BaseName, decode, encode } from 'multibase'
6import type { VerificationMethod } from 'did-resolver'
7import { secp256k1 } from '@noble/curves/secp256k1'
8import { p256 } from '@noble/curves/p256'
9
10const u8a = { toString, fromString, concat }
11
12/**
13 * @deprecated Signers will be expected to return base64url `string` signatures.
14 */
15export interface EcdsaSignature {
16 r: string
17 s: string
18 recoveryParam?: number
19}
20
21/**
22 * @deprecated Signers will be expected to return base64url `string` signatures.
23 */
24export type ECDSASignature = {
25 compact: Uint8Array
26 recovery?: number
27}
28
29export type JsonWebKey = {
30 crv: string
31 kty: string
32 x?: string
33 y?: string
34 // eslint-disable-next-line @typescript-eslint/no-explicit-any
35 [key: string]: any
36}
37
38export function bytesToBase64url(b: Uint8Array): string {
39 return u8a.toString(b, 'base64url')
40}
41
42export function base64ToBytes(s: string): Uint8Array {
43 const inputBase64Url = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
44 return u8a.fromString(inputBase64Url, 'base64url')
45}
46
47export function bytesToBase64(b: Uint8Array): string {
48 return u8a.toString(b, 'base64pad')
49}
50
51export function base58ToBytes(s: string): Uint8Array {
52 return u8a.fromString(s, 'base58btc')
53}
54
55export function bytesToBase58(b: Uint8Array): string {
56 return u8a.toString(b, 'base58btc')
57}
58
59export type KNOWN_JWA = 'ES256' | 'ES256K' | 'ES256K-R' | 'Ed25519' | 'EdDSA'
60
61export type KNOWN_VERIFICATION_METHOD =
62 | 'JsonWebKey2020'
63 | 'Multikey'
64 | 'Secp256k1SignatureVerificationKey2018' // deprecated in favor of EcdsaSecp256k1VerificationKey2019
65 | 'Secp256k1VerificationKey2018' // deprecated in favor of EcdsaSecp256k1VerificationKey2019
66 | 'EcdsaSecp256k1VerificationKey2019' // ES256K / ES256K-R
67 | 'EcdsaPublicKeySecp256k1' // deprecated in favor of EcdsaSecp256k1VerificationKey2019
68 | 'EcdsaSecp256k1RecoveryMethod2020' // ES256K-R (ES256K also supported with 1 less bit of security)
69 | 'EcdsaSecp256r1VerificationKey2019' // ES256 / P-256
70 | 'Ed25519VerificationKey2018'
71 | 'Ed25519VerificationKey2020'
72 | 'ED25519SignatureVerification' // deprecated
73 | 'ConditionalProof2022'
74 | 'X25519KeyAgreementKey2019' // deprecated
75 | 'X25519KeyAgreementKey2020'
76
77export type KNOWN_KEY_TYPE = 'Secp256k1' | 'Ed25519' | 'X25519' | 'Bls12381G1' | 'Bls12381G2' | 'P-256'
78
79export type PublicKeyTypes = Record<KNOWN_JWA, KNOWN_VERIFICATION_METHOD[]>
80
81export const SUPPORTED_PUBLIC_KEY_TYPES: PublicKeyTypes = {
82 ES256: ['JsonWebKey2020', 'Multikey', 'EcdsaSecp256r1VerificationKey2019'],
83 ES256K: [
84 'EcdsaSecp256k1VerificationKey2019',
85 /**
86 * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress
87 */
88 'EcdsaSecp256k1RecoveryMethod2020',
89 /**
90 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
91 * not an ethereumAddress
92 */
93 'Secp256k1VerificationKey2018',
94 /**
95 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
96 * not an ethereumAddress
97 */
98 'Secp256k1SignatureVerificationKey2018',
99 /**
100 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
101 * not an ethereumAddress
102 */
103 'EcdsaPublicKeySecp256k1',
104 /**
105 * TODO - support R1 key as well
106 * 'ConditionalProof2022',
107 */
108 'JsonWebKey2020',
109 'Multikey',
110 ],
111 'ES256K-R': [
112 'EcdsaSecp256k1VerificationKey2019',
113 /**
114 * Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress
115 */
116 'EcdsaSecp256k1RecoveryMethod2020',
117 /**
118 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
119 * not an ethereumAddress
120 */
121 'Secp256k1VerificationKey2018',
122 /**
123 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
124 * not an ethereumAddress
125 */
126 'Secp256k1SignatureVerificationKey2018',
127 /**
128 * @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
129 * not an ethereumAddress
130 */
131 'EcdsaPublicKeySecp256k1',
132 'ConditionalProof2022',
133 'JsonWebKey2020',
134 'Multikey',
135 ],
136 Ed25519: [
137 'ED25519SignatureVerification',
138 'Ed25519VerificationKey2018',
139 'Ed25519VerificationKey2020',
140 'JsonWebKey2020',
141 'Multikey',
142 ],
143 EdDSA: [
144 'ED25519SignatureVerification',
145 'Ed25519VerificationKey2018',
146 'Ed25519VerificationKey2020',
147 'JsonWebKey2020',
148 'Multikey',
149 ],
150}
151
152export const VM_TO_KEY_TYPE: Record<KNOWN_VERIFICATION_METHOD, KNOWN_KEY_TYPE | undefined> = {
153 Secp256k1SignatureVerificationKey2018: 'Secp256k1',
154 Secp256k1VerificationKey2018: 'Secp256k1',
155 EcdsaSecp256k1VerificationKey2019: 'Secp256k1',
156 EcdsaPublicKeySecp256k1: 'Secp256k1',
157 EcdsaSecp256k1RecoveryMethod2020: 'Secp256k1',
158 EcdsaSecp256r1VerificationKey2019: 'P-256',
159 Ed25519VerificationKey2018: 'Ed25519',
160 Ed25519VerificationKey2020: 'Ed25519',
161 ED25519SignatureVerification: 'Ed25519',
162 X25519KeyAgreementKey2019: 'X25519',
163 X25519KeyAgreementKey2020: 'X25519',
164 ConditionalProof2022: undefined,
165 JsonWebKey2020: undefined, // key type must be specified in the JWK
166 Multikey: undefined, // key type must be extracted from the multicodec
167}
168
169export type KNOWN_CODECS =
170 | 'ed25519-pub'
171 | 'x25519-pub'
172 | 'secp256k1-pub'
173 | 'bls12_381-g1-pub'
174 | 'bls12_381-g2-pub'
175 | 'p256-pub'
176
177// this is from the multicodec table https://github.com/multiformats/multicodec/blob/master/table.csv
178export const supportedCodecs: Record<KNOWN_CODECS, number> = {
179 'ed25519-pub': 0xed,
180 'x25519-pub': 0xec,
181 'secp256k1-pub': 0xe7,
182 'bls12_381-g1-pub': 0xea,
183 'bls12_381-g2-pub': 0xeb,
184 'p256-pub': 0x1200,
185}
186
187export const CODEC_TO_KEY_TYPE: Record<KNOWN_CODECS, KNOWN_KEY_TYPE> = {
188 'bls12_381-g1-pub': 'Bls12381G1',
189 'bls12_381-g2-pub': 'Bls12381G2',
190 'ed25519-pub': 'Ed25519',
191 'p256-pub': 'P-256',
192 'secp256k1-pub': 'Secp256k1',
193 'x25519-pub': 'X25519',
194}
195
196/**
197 * Extracts the raw byte representation of a public key from a VerificationMethod along with an inferred key type
198 * @param pk a VerificationMethod entry from a DIDDocument
199 * @return an object containing the `keyBytes` of the public key and an inferred `keyType`
200 */
201export function extractPublicKeyBytes(pk: VerificationMethod): { keyBytes: Uint8Array; keyType?: KNOWN_KEY_TYPE } {
202 if (pk.publicKeyBase58) {
203 return {
204 keyBytes: base58ToBytes(pk.publicKeyBase58),
205 keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD],
206 }
207 } else if (pk.publicKeyBase64) {
208 return {
209 keyBytes: base64ToBytes(pk.publicKeyBase64),
210 keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD],
211 }
212 } else if (pk.publicKeyHex) {
213 return { keyBytes: hexToBytes(pk.publicKeyHex), keyType: VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD] }
214 } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'secp256k1' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) {
215 return {
216 keyBytes: secp256k1.ProjectivePoint.fromAffine({
217 x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)),
218 y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)),
219 }).toRawBytes(false),
220 keyType: 'Secp256k1',
221 }
222 } else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'P-256' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) {
223 return {
224 keyBytes: p256.ProjectivePoint.fromAffine({
225 x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)),
226 y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y)),
227 }).toRawBytes(false),
228 keyType: 'P-256',
229 }
230 } else if (
231 pk.publicKeyJwk &&
232 pk.publicKeyJwk.kty === 'OKP' &&
233 ['Ed25519', 'X25519'].includes(pk.publicKeyJwk.crv ?? '') &&
234 pk.publicKeyJwk.x
235 ) {
236 return { keyBytes: base64ToBytes(pk.publicKeyJwk.x), keyType: pk.publicKeyJwk.crv as KNOWN_KEY_TYPE }
237 } else if (pk.publicKeyMultibase) {
238 const { keyBytes, keyType } = multibaseToBytes(pk.publicKeyMultibase)
239 return { keyBytes, keyType: keyType ?? VM_TO_KEY_TYPE[pk.type as KNOWN_VERIFICATION_METHOD] }
240 }
241 return { keyBytes: new Uint8Array() }
242}
243
244/**
245 * Encodes the given byte array to a multibase string (defaulting to base58btc).
246 * If a codec is provided, the corresponding multicodec prefix will be added.
247 *
248 * @param b - the Uint8Array to be encoded
249 * @param base - the base to use for encoding (defaults to base58btc)
250 * @param codec - the codec to use for encoding (defaults to no codec)
251 *
252 * @returns the multibase encoded string
253 *
254 * @public
255 */
256export function bytesToMultibase(
257 b: Uint8Array,
258 base: BaseName = 'base58btc',
259 codec?: keyof typeof supportedCodecs | number
260): string {
261 if (!codec) {
262 return u8a.toString(encode(base, b), 'utf-8')
263 } else {
264 const codecCode = typeof codec === 'string' ? supportedCodecs[codec] : codec
265 const prefixLength = varint.encodingLength(codecCode)
266 const multicodecEncoding = new Uint8Array(prefixLength + b.length)
267 varint.encodeTo(codecCode, multicodecEncoding) // set prefix
268 multicodecEncoding.set(b, prefixLength) // add the original bytes
269 return u8a.toString(encode(base, multicodecEncoding), 'utf-8')
270 }
271}
272
273/**
274 * Converts a multibase string to the Uint8Array it represents.
275 * This method will assume the byte array that is multibase encoded is a multicodec and will attempt to decode it.
276 *
277 * @param s - the string to be converted
278 *
279 * @throws if the string is not formatted correctly.
280 *
281 * @public
282 */
283export function multibaseToBytes(s: string): { keyBytes: Uint8Array; keyType?: KNOWN_KEY_TYPE } {
284 const bytes = decode(s)
285
286 // look for known key lengths first
287 // Ed25519/X25519, secp256k1/P256 compressed or not, BLS12-381 G1/G2 compressed
288 if ([32, 33, 48, 64, 65, 96].includes(bytes.length)) {
289 return { keyBytes: bytes }
290 }
291
292 // then assume multicodec, otherwise return the bytes
293 try {
294 // eslint-disable-next-line @typescript-eslint/no-unused-vars
295 const [codec, length] = varint.decode(bytes)
296 const possibleCodec: string | undefined =
297 Object.entries(supportedCodecs).filter(([, code]) => code === codec)?.[0][0] ?? ''
298 return { keyBytes: bytes.slice(length), keyType: CODEC_TO_KEY_TYPE[possibleCodec as KNOWN_CODECS] }
299 } catch (e) {
300 // not a multicodec, return the bytes
301 return { keyBytes: bytes }
302 }
303}
304
305export function hexToBytes(s: string, minLength?: number): Uint8Array {
306 let input = s.startsWith('0x') ? s.substring(2) : s
307
308 if (input.length % 2 !== 0) {
309 input = `0${input}`
310 }
311
312 if (minLength) {
313 const paddedLength = Math.max(input.length, minLength * 2)
314 input = input.padStart(paddedLength, '00')
315 }
316
317 return u8a.fromString(input.toLowerCase(), 'base16')
318}
319
320export function encodeBase64url(s: string): string {
321 return bytesToBase64url(u8a.fromString(s))
322}
323
324export function decodeBase64url(s: string): string {
325 return u8a.toString(base64ToBytes(s))
326}
327
328export function bytesToHex(b: Uint8Array): string {
329 return u8a.toString(b, 'base16')
330}
331
332export function bytesToBigInt(b: Uint8Array): bigint {
333 return BigInt(`0x` + u8a.toString(b, 'base16'))
334}
335
336export function bigintToBytes(n: bigint, minLength?: number): Uint8Array {
337 return hexToBytes(n.toString(16), minLength)
338}
339
340export function stringToBytes(s: string): Uint8Array {
341 return u8a.fromString(s, 'utf-8')
342}
343
344export function toJose({ r, s, recoveryParam }: EcdsaSignature, recoverable?: boolean): string {
345 const jose = new Uint8Array(recoverable ? 65 : 64)
346 jose.set(u8a.fromString(r, 'base16'), 0)
347 jose.set(u8a.fromString(s, 'base16'), 32)
348 if (recoverable) {
349 if (typeof recoveryParam === 'undefined') {
350 throw new Error('Signer did not return a recoveryParam')
351 }
352 jose[64] = <number>recoveryParam
353 }
354 return bytesToBase64url(jose)
355}
356
357export function fromJose(signature: string): { r: string; s: string; recoveryParam?: number } {
358 const signatureBytes: Uint8Array = base64ToBytes(signature)
359 if (signatureBytes.length < 64 || signatureBytes.length > 65) {
360 throw new TypeError(`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`)
361 }
362 const r = bytesToHex(signatureBytes.slice(0, 32))
363 const s = bytesToHex(signatureBytes.slice(32, 64))
364 const recoveryParam = signatureBytes.length === 65 ? signatureBytes[64] : undefined
365 return { r, s, recoveryParam }
366}
367
368export function toSealed(ciphertext: string, tag?: string): Uint8Array {
369 return u8a.concat([base64ToBytes(ciphertext), tag ? base64ToBytes(tag) : new Uint8Array(0)])
370}
371
372export function leftpad(data: string, size = 64): string {
373 if (data.length === size) return data
374 return '0'.repeat(size - data.length) + data
375}
376
377/**
378 * Generate random x25519 key pair.
379 */
380export function generateKeyPair(): { secretKey: Uint8Array; publicKey: Uint8Array } {
381 const secretKey = x25519.utils.randomPrivateKey()
382 const publicKey = x25519.getPublicKey(secretKey)
383 return {
384 secretKey: secretKey,
385 publicKey: publicKey,
386 }
387}
388
389/**
390 * Generate private-public x25519 key pair from `seed`.
391 */
392export function generateKeyPairFromSeed(seed: Uint8Array): { secretKey: Uint8Array; publicKey: Uint8Array } {
393 if (seed.length !== 32) {
394 throw new Error(`x25519: seed must be ${32} bytes`)
395 }
396 return {
397 publicKey: x25519.getPublicKey(seed),
398 secretKey: seed,
399 }
400}
401
402export function genX25519EphemeralKeyPair(): EphemeralKeyPair {
403 const epk = generateKeyPair()
404 return {
405 publicKeyJWK: { kty: 'OKP', crv: 'X25519', x: bytesToBase64url(epk.publicKey) },
406 secretKey: epk.secretKey,
407 }
408}
409
410/**
411 * Checks if a variable is defined and not null.
412 * After this check, typescript sees the variable as defined.
413 *
414 * @param arg - The input to be verified
415 *
416 * @returns true if the input variable is defined.
417 */
418export function isDefined<T>(arg: T): arg is Exclude<T, null | undefined> {
419 return arg !== null && typeof arg !== 'undefined'
420}