11import { ed25519 as ed } from '@noble/curves/ed25519'
2+ import { toString as uint8arrayToString } from 'uint8arrays/to-string'
3+ import crypto from '../../webcrypto/index.js'
24import type { Uint8ArrayKeyPair } from '../interface.js'
35import type { Uint8ArrayList } from 'uint8arraylist'
46
@@ -9,6 +11,17 @@ const KEYS_BYTE_LENGTH = 32
911export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
1012export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
1113
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+
1225export function generateKey ( ) : Uint8ArrayKeyPair {
1326 // the actual private key (32 bytes)
1427 const privateKeyRaw = ed . utils . randomPrivateKey ( )
@@ -23,9 +36,6 @@ export function generateKey (): Uint8ArrayKeyPair {
2336 }
2437}
2538
26- /**
27- * Generate keypair from a 32 byte uint8array
28- */
2939export function generateKeyFromSeed ( seed : Uint8Array ) : Uint8ArrayKeyPair {
3040 if ( seed . length !== KEYS_BYTE_LENGTH ) {
3141 throw new TypeError ( '"seed" must be 32 bytes in length.' )
@@ -45,16 +55,73 @@ export function generateKeyFromSeed (seed: Uint8Array): Uint8ArrayKeyPair {
4555 }
4656}
4757
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 {
4982 const privateKeyRaw = privateKey . subarray ( 0 , KEYS_BYTE_LENGTH )
5083
5184 return ed . sign ( msg instanceof Uint8Array ? msg : msg . subarray ( ) , privateKeyRaw )
5285}
5386
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 {
55110 return ed . verify ( sig , msg instanceof Uint8Array ? msg : msg . subarray ( ) , publicKey )
56111}
57112
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+
58125function concatKeys ( privateKeyRaw : Uint8Array , publicKey : Uint8Array ) : Uint8Array {
59126 const privateKey = new Uint8Array ( PRIVATE_KEY_BYTE_LENGTH )
60127 for ( let i = 0 ; i < KEYS_BYTE_LENGTH ; i ++ ) {
0 commit comments