@sphereon/ssi-sdk.credential-vcdm2-jose-provider 0.33.1-feature.jose.vcdm.59

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