1
- import * as ed from '@noble/ed25519'
1
+ import crypto from 'crypto'
2
+ import { promisify } from 'util'
3
+ import { toString as uint8arrayToString } from 'uint8arrays/to-string'
4
+ import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'
5
+
6
+ const keypair = promisify ( crypto . generateKeyPair )
2
7
3
8
const PUBLIC_KEY_BYTE_LENGTH = 32
4
9
const PRIVATE_KEY_BYTE_LENGTH = 64 // private key is actually 32 bytes but for historical reasons we concat private and public keys
5
10
const KEYS_BYTE_LENGTH = 32
11
+ const SIGNATURE_BYTE_LENGTH = 64
6
12
7
13
export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
8
14
export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
9
15
16
+ function derivePublicKey ( privateKey : Uint8Array ) {
17
+ const hash = crypto . createHash ( 'sha512' )
18
+ hash . update ( privateKey )
19
+ return hash . digest ( ) . subarray ( 32 )
20
+ }
21
+
10
22
export async function generateKey ( ) {
11
- // the actual private key (32 bytes)
12
- const privateKeyRaw = ed . utils . randomPrivateKey ( )
13
- const publicKey = await ed . getPublicKey ( privateKeyRaw )
23
+ const key = await keypair ( 'ed25519' , {
24
+ publicKeyEncoding : { type : 'spki' , format : 'jwk' } ,
25
+ privateKeyEncoding : { type : 'pkcs8' , format : 'jwk' }
26
+ } )
14
27
15
- // concatenated the public key to the private key
16
- const privateKey = concatKeys ( privateKeyRaw , publicKey )
28
+ // @ts -expect-error node types are missing jwk as a format
29
+ const privateKeyRaw = uint8arrayFromString ( key . privateKey . d , 'base64url' )
30
+ // @ts -expect-error node types are missing jwk as a format
31
+ const publicKeyRaw = uint8arrayFromString ( key . privateKey . x , 'base64url' )
17
32
18
33
return {
19
- privateKey,
20
- publicKey
34
+ privateKey : concatKeys ( privateKeyRaw , publicKeyRaw ) ,
35
+ publicKey : publicKeyRaw
21
36
}
22
37
}
23
38
@@ -32,25 +47,68 @@ export async function generateKeyFromSeed (seed: Uint8Array) {
32
47
}
33
48
34
49
// based on node forges algorithm, the seed is used directly as private key
35
- const privateKeyRaw = seed
36
- const publicKey = await ed . getPublicKey ( privateKeyRaw )
37
-
38
- const privateKey = concatKeys ( privateKeyRaw , publicKey )
50
+ const publicKeyRaw = derivePublicKey ( seed )
39
51
40
52
return {
41
- privateKey,
42
- publicKey
53
+ privateKey : concatKeys ( seed , publicKeyRaw ) ,
54
+ publicKey : publicKeyRaw
43
55
}
44
56
}
45
57
46
- export async function hashAndSign ( privateKey : Uint8Array , msg : Uint8Array ) {
47
- const privateKeyRaw = privateKey . slice ( 0 , KEYS_BYTE_LENGTH )
58
+ export async function hashAndSign ( key : Uint8Array , msg : Uint8Array ) {
59
+ if ( ! ( key instanceof Uint8Array ) ) {
60
+ throw new TypeError ( '"key" must be a node.js Buffer, or Uint8Array.' )
61
+ }
62
+
63
+ let privateKey : Uint8Array
64
+ let publicKey : Uint8Array
65
+
66
+ if ( key . byteLength === PRIVATE_KEY_BYTE_LENGTH ) {
67
+ privateKey = key . subarray ( 0 , 32 )
68
+ publicKey = key . subarray ( 32 )
69
+ } else if ( key . byteLength === KEYS_BYTE_LENGTH ) {
70
+ privateKey = key . subarray ( 0 , 32 )
71
+ publicKey = derivePublicKey ( privateKey )
72
+ } else {
73
+ throw new TypeError ( '"key" must be 64 or 32 bytes in length.' )
74
+ }
75
+
76
+ const obj = crypto . createPrivateKey ( {
77
+ format : 'jwk' ,
78
+ key : {
79
+ crv : 'Ed25519' ,
80
+ d : uint8arrayToString ( privateKey , 'base64url' ) ,
81
+ x : uint8arrayToString ( publicKey , 'base64url' ) ,
82
+ kty : 'OKP'
83
+ }
84
+ } )
48
85
49
- return await ed . sign ( msg , privateKeyRaw )
86
+ return crypto . sign ( null , msg , obj )
50
87
}
51
88
52
- export async function hashAndVerify ( publicKey : Uint8Array , sig : Uint8Array , msg : Uint8Array ) {
53
- return await ed . verify ( sig , msg , publicKey )
89
+ export async function hashAndVerify ( key : Uint8Array , sig : Uint8Array , msg : Uint8Array ) {
90
+ if ( key . byteLength !== PUBLIC_KEY_BYTE_LENGTH ) {
91
+ throw new TypeError ( '"key" must be 32 bytes in length.' )
92
+ } else if ( ! ( key instanceof Uint8Array ) ) {
93
+ throw new TypeError ( '"key" must be a node.js Buffer, or Uint8Array.' )
94
+ }
95
+
96
+ if ( sig . byteLength !== SIGNATURE_BYTE_LENGTH ) {
97
+ throw new TypeError ( '"sig" must be 64 bytes in length.' )
98
+ } else if ( ! ( sig instanceof Uint8Array ) ) {
99
+ throw new TypeError ( '"sig" must be a node.js Buffer, or Uint8Array.' )
100
+ }
101
+
102
+ const obj = crypto . createPublicKey ( {
103
+ format : 'jwk' ,
104
+ key : {
105
+ crv : 'Ed25519' ,
106
+ x : uint8arrayToString ( key , 'base64url' ) ,
107
+ kty : 'OKP'
108
+ }
109
+ } )
110
+
111
+ return crypto . verify ( null , msg , obj , sig )
54
112
}
55
113
56
114
function concatKeys ( privateKeyRaw : Uint8Array , publicKey : Uint8Array ) {
0 commit comments