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 )
27
38const PUBLIC_KEY_BYTE_LENGTH = 32
49const PRIVATE_KEY_BYTE_LENGTH = 64 // private key is actually 32 bytes but for historical reasons we concat private and public keys
510const KEYS_BYTE_LENGTH = 32
11+ const SIGNATURE_BYTE_LENGTH = 64
612
713export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
814export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
915
16+ function derivePublicKey ( privateKey : Uint8Array ) {
17+ const hash = crypto . createHash ( 'sha512' )
18+ hash . update ( privateKey )
19+ return hash . digest ( ) . subarray ( 32 )
20+ }
21+
1022export 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+ } )
1427
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' )
1732
1833 return {
19- privateKey,
20- publicKey
34+ privateKey : concatKeys ( privateKeyRaw , publicKeyRaw ) ,
35+ publicKey : publicKeyRaw
2136 }
2237}
2338
@@ -32,25 +47,68 @@ export async function generateKeyFromSeed (seed: Uint8Array) {
3247 }
3348
3449 // 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 )
3951
4052 return {
41- privateKey,
42- publicKey
53+ privateKey : concatKeys ( seed , publicKeyRaw ) ,
54+ publicKey : publicKeyRaw
4355 }
4456}
4557
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+ } )
4885
49- return await ed . sign ( msg , privateKeyRaw )
86+ return crypto . sign ( null , msg , obj )
5087}
5188
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 )
54112}
55113
56114function concatKeys ( privateKeyRaw : Uint8Array , publicKey : Uint8Array ) {
0 commit comments