1
1
import { ed25519 as ed } from '@noble/curves/ed25519'
2
+ import { toString as uint8arrayToString } from 'uint8arrays/to-string'
3
+ import crypto from '../../webcrypto/index.js'
2
4
import type { Uint8ArrayKeyPair } from '../interface.js'
3
5
import type { Uint8ArrayList } from 'uint8arraylist'
4
6
@@ -9,6 +11,17 @@ const KEYS_BYTE_LENGTH = 32
9
11
export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
10
12
export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
11
13
14
+ // memoize support result to skip additional awaits every time we use an ed key
15
+ let ed25519Supported : boolean | undefined
16
+ const webCryptoEd25519SupportedPromise = ( async ( ) => {
17
+ try {
18
+ await crypto . get ( ) . subtle . generateKey ( { name : 'Ed25519' } , true , [ 'sign' , 'verify' ] )
19
+ return true
20
+ } catch {
21
+ return false
22
+ }
23
+ } ) ( )
24
+
12
25
export function generateKey ( ) : Uint8ArrayKeyPair {
13
26
// the actual private key (32 bytes)
14
27
const privateKeyRaw = ed . utils . randomPrivateKey ( )
@@ -23,9 +36,6 @@ export function generateKey (): Uint8ArrayKeyPair {
23
36
}
24
37
}
25
38
26
- /**
27
- * Generate keypair from a 32 byte uint8array
28
- */
29
39
export function generateKeyFromSeed ( seed : Uint8Array ) : Uint8ArrayKeyPair {
30
40
if ( seed . length !== KEYS_BYTE_LENGTH ) {
31
41
throw new TypeError ( '"seed" must be 32 bytes in length.' )
@@ -45,16 +55,73 @@ export function generateKeyFromSeed (seed: Uint8Array): Uint8ArrayKeyPair {
45
55
}
46
56
}
47
57
48
- export function hashAndSign ( privateKey : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Uint8Array {
58
+ async function hashAndSignWebCrypto ( privateKey : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Promise < Uint8Array > {
59
+ let privateKeyRaw : Uint8Array
60
+ if ( privateKey . length === PRIVATE_KEY_BYTE_LENGTH ) {
61
+ privateKeyRaw = privateKey . subarray ( 0 , 32 )
62
+ } else {
63
+ privateKeyRaw = privateKey
64
+ }
65
+
66
+ const jwk : JsonWebKey = {
67
+ crv : 'Ed25519' ,
68
+ kty : 'OKP' ,
69
+ x : uint8arrayToString ( privateKey . subarray ( 32 ) , 'base64url' ) ,
70
+ d : uint8arrayToString ( privateKeyRaw , 'base64url' ) ,
71
+ ext : true ,
72
+ key_ops : [ 'sign' ]
73
+ }
74
+
75
+ const key = await crypto . get ( ) . subtle . importKey ( 'jwk' , jwk , { name : 'Ed25519' } , true , [ 'sign' ] )
76
+ const sig = await crypto . get ( ) . subtle . sign ( { name : 'Ed25519' } , key , msg instanceof Uint8Array ? msg : msg . subarray ( ) )
77
+
78
+ return new Uint8Array ( sig , 0 , sig . byteLength )
79
+ }
80
+
81
+ function hashAndSignNoble ( privateKey : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Uint8Array {
49
82
const privateKeyRaw = privateKey . subarray ( 0 , KEYS_BYTE_LENGTH )
50
83
51
84
return ed . sign ( msg instanceof Uint8Array ? msg : msg . subarray ( ) , privateKeyRaw )
52
85
}
53
86
54
- export function hashAndVerify ( publicKey : Uint8Array , sig : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : boolean {
87
+ export async function hashAndSign ( privateKey : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Promise < Uint8Array > {
88
+ if ( ed25519Supported == null ) {
89
+ ed25519Supported = await webCryptoEd25519SupportedPromise
90
+ }
91
+
92
+ if ( ed25519Supported ) {
93
+ return hashAndSignWebCrypto ( privateKey , msg )
94
+ }
95
+
96
+ return hashAndSignNoble ( privateKey , msg )
97
+ }
98
+
99
+ async function hashAndVerifyWebCrypto ( publicKey : Uint8Array , sig : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Promise < boolean > {
100
+ if ( publicKey . buffer instanceof ArrayBuffer ) {
101
+ const key = await crypto . get ( ) . subtle . importKey ( 'raw' , publicKey . buffer , { name : 'Ed25519' } , false , [ 'verify' ] )
102
+ const isValid = await crypto . get ( ) . subtle . verify ( { name : 'Ed25519' } , key , sig , msg instanceof Uint8Array ? msg : msg . subarray ( ) )
103
+ return isValid
104
+ }
105
+
106
+ throw new TypeError ( 'WebCrypto does not support SharedArrayBuffer for Ed25519 keys' )
107
+ }
108
+
109
+ function hashAndVerifyNoble ( publicKey : Uint8Array , sig : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : boolean {
55
110
return ed . verify ( sig , msg instanceof Uint8Array ? msg : msg . subarray ( ) , publicKey )
56
111
}
57
112
113
+ export async function hashAndVerify ( publicKey : Uint8Array , sig : Uint8Array , msg : Uint8Array | Uint8ArrayList ) : Promise < boolean > {
114
+ if ( ed25519Supported == null ) {
115
+ ed25519Supported = await webCryptoEd25519SupportedPromise
116
+ }
117
+
118
+ if ( ed25519Supported ) {
119
+ return hashAndVerifyWebCrypto ( publicKey , sig , msg )
120
+ }
121
+
122
+ return hashAndVerifyNoble ( publicKey , sig , msg )
123
+ }
124
+
58
125
function concatKeys ( privateKeyRaw : Uint8Array , publicKey : Uint8Array ) : Uint8Array {
59
126
const privateKey = new Uint8Array ( PRIVATE_KEY_BYTE_LENGTH )
60
127
for ( let i = 0 ; i < KEYS_BYTE_LENGTH ; i ++ ) {
0 commit comments