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