@@ -15,55 +15,83 @@ You should have received a copy of the GNU Lesser General Public License
1515along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616*/
1717
18+ import { TransactionFactory , TypedTransaction } from '@ethereumjs/tx' ;
19+ import { decrypt as createDecipheriv , encrypt as createCipheriv } from 'ethereum-cryptography/aes' ;
20+ import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2' ;
21+ import { scryptSync } from 'ethereum-cryptography/scrypt' ;
22+ import { getPublicKey , recoverPublicKey , signSync , utils } from 'ethereum-cryptography/secp256k1' ;
1823import {
24+ InvalidKdfError ,
25+ InvalidPasswordError ,
1926 InvalidPrivateKeyError ,
20- PrivateKeyLengthError ,
21- UndefinedRawTransactionError ,
22- SignerError ,
2327 InvalidSignatureError ,
24- InvalidKdfError ,
28+ IVLengthError ,
2529 KeyDerivationError ,
2630 KeyStoreVersionError ,
27- InvalidPasswordError ,
28- IVLengthError ,
2931 PBKDF2IterationsError ,
32+ PrivateKeyLengthError ,
33+ SignerError ,
34+ UndefinedRawTransactionError ,
3035} from 'web3-errors' ;
31- import { utils , getPublicKey } from 'ethereum-cryptography/secp256k1' ;
32- import { keccak256 } from 'ethereum-cryptography/keccak' ;
33- import { TransactionFactory , TypedTransaction } from '@ethereumjs/tx' ;
34- import { ecdsaSign , ecdsaRecover } from 'secp256k1' ;
35- import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2' ;
36- import { scryptSync } from 'ethereum-cryptography/scrypt' ;
37- import { encrypt as createCipheriv , decrypt as createDecipheriv } from 'ethereum-cryptography/aes' ;
3836import {
39- toChecksumAddress ,
37+ Address ,
38+ Bytes ,
39+ bytesToBuffer ,
4040 bytesToHex ,
41- sha3Raw ,
4241 HexString ,
43- randomBytes ,
4442 hexToBytes ,
45- Address ,
4643 isHexStrict ,
44+ numberToHex ,
45+ randomBytes ,
46+ sha3Raw ,
47+ toChecksumAddress ,
4748 utf8ToHex ,
4849} from 'web3-utils' ;
49- import { validator , isBuffer , isHexString32Bytes , isString , isNullish } from 'web3-validator' ;
50+ import { isBuffer , isNullish , isString , validator } from 'web3-validator' ;
51+ import { keyStoreSchema } from './schemas' ;
5052import {
53+ CipherOptions ,
54+ KeyStore ,
55+ PBKDF2SHA256Params ,
56+ ScryptParams ,
5157 SignatureObject ,
5258 SignResult ,
5359 SignTransactionResult ,
54- KeyStore ,
55- ScryptParams ,
56- PBKDF2SHA256Params ,
57- CipherOptions ,
5860 Web3Account ,
5961} from './types' ;
60- import { keyStoreSchema } from './schemas' ;
62+
63+ /**
64+ * Get the private key buffer after the validation
65+ *
66+ * @param data - The data in any bytes format
67+ * @returns
68+ */
69+ export const parseAndValidatePrivateKey = ( data : Bytes ) : Buffer => {
70+ let privateKeyBuffer : Buffer ;
71+
72+ // To avoid the case of 1 character less in a hex string which is prefixed with '0' by using 'bytesToBuffer'
73+ if ( typeof data === 'string' && isHexStrict ( data ) && data . length !== 66 ) {
74+ throw new PrivateKeyLengthError ( ) ;
75+ }
76+
77+ try {
78+ privateKeyBuffer = Buffer . isBuffer ( data ) ? data : bytesToBuffer ( data ) ;
79+ } catch {
80+ throw new InvalidPrivateKeyError ( ) ;
81+ }
82+
83+ if ( privateKeyBuffer . byteLength !== 32 ) {
84+ throw new PrivateKeyLengthError ( ) ;
85+ }
86+
87+ return privateKeyBuffer ;
88+ } ;
6189
6290/**
6391 *
6492 * Hashes the given message. The data will be UTF-8 HEX decoded and enveloped as follows: "\x19Ethereum Signed Message:\n" + message.length + message and hashed using keccak256.
6593 *
66- * @param message A message to hash, if its HEX it will be UTF8 decoded.
94+ * @param message - A message to hash, if its HEX it will be UTF8 decoded.
6795 * @returns The hashed message
6896 * ```ts
6997 * hashMessage("Hello world")
@@ -77,11 +105,14 @@ export const hashMessage = (message: string): string => {
77105
78106 const messageBytes = hexToBytes ( messageHex ) ;
79107
80- const preamble = `\x19Ethereum Signed Message:\n${ messageBytes . length } ` ;
108+ const preamble = Buffer . from (
109+ `\x19Ethereum Signed Message:\n${ messageBytes . byteLength } ` ,
110+ 'utf8' ,
111+ ) ;
81112
82- const ethMessage = Buffer . concat ( [ Buffer . from ( preamble ) , Buffer . from ( messageBytes ) ] ) ;
113+ const ethMessage = Buffer . concat ( [ preamble , messageBytes ] ) ;
83114
84- return `0x ${ Buffer . from ( keccak256 ( ethMessage ) ) . toString ( 'hex' ) } ` ;
115+ return sha3Raw ( ethMessage ) ; // using keccak in web3-utils.sha3Raw instead of SHA3 (NIST Standard) as both are different
85116} ;
86117
87118/**
@@ -103,31 +134,30 @@ export const hashMessage = (message: string): string => {
103134 * }
104135 * ```
105136 */
106- export const sign = ( data : string , privateKey : HexString ) : SignResult => {
107- const privateKeyParam = privateKey . startsWith ( '0x' ) ? privateKey . substring ( 2 ) : privateKey ;
108-
109- if ( ! isHexString32Bytes ( privateKeyParam , false ) ) {
110- throw new PrivateKeyLengthError ( ) ;
111- }
137+ export const sign = ( data : string , privateKey : Bytes ) : SignResult => {
138+ const privateKeyBuffer = parseAndValidatePrivateKey ( privateKey ) ;
112139
113140 const hash = hashMessage ( data ) ;
114141
115- const signObj = ecdsaSign (
116- Buffer . from ( hash . substring ( 2 ) , 'hex' ) ,
117- Buffer . from ( privateKeyParam , 'hex' ) ,
118- ) ;
142+ const [ signature , recoverId ] = signSync ( hash . substring ( 2 ) , privateKeyBuffer , {
143+ // Makes signatures compatible with libsecp256k1
144+ recovered : true ,
145+
146+ // Returned signature should be in DER format ( non compact )
147+ der : false ,
148+ } ) ;
119149
120- const r = Buffer . from ( signObj . signature . slice ( 0 , 32 ) ) ;
121- const s = Buffer . from ( signObj . signature . slice ( 32 , 64 ) ) ;
122- const v = signObj . recid + 27 ;
150+ const r = Buffer . from ( signature . slice ( 0 , 32 ) ) ;
151+ const s = Buffer . from ( signature . slice ( 32 , 64 ) ) ;
152+ const v = recoverId + 27 ;
123153
124154 return {
125155 message : data ,
126156 messageHash : hash ,
127- v : `0x ${ v . toString ( 16 ) } ` ,
128- r : `0x ${ r . toString ( 'hex' ) } ` ,
129- s : `0x ${ s . toString ( 'hex' ) } ` ,
130- signature : `0x${ Buffer . from ( signObj . signature ) . toString ( 'hex' ) } ${ v . toString ( 16 ) } ` ,
157+ v : numberToHex ( v ) ,
158+ r : bytesToHex ( r ) ,
159+ s : bytesToHex ( s ) ,
160+ signature : `0x${ Buffer . from ( signature ) . toString ( 'hex' ) } ${ v . toString ( 16 ) } ` ,
131161 } ;
132162} ;
133163
@@ -234,7 +264,7 @@ export const signTransaction = async (
234264 }
235265
236266 const rawTx = bytesToHex ( signedTx . serialize ( ) ) ;
237- const txHash = keccak256 ( hexToBytes ( rawTx ) ) ;
267+ const txHash = sha3Raw ( rawTx ) ; // using keccak in web3-utils.sha3Raw instead of SHA3 (NIST Standard) as both are different
238268
239269 return {
240270 messageHash : bytesToHex ( Buffer . from ( signedTx . getMessageToSign ( true ) ) ) ,
@@ -302,10 +332,10 @@ export const recover = (
302332
303333 const v = signature . substring ( V_INDEX ) ; // 0x + r + s + v
304334
305- const ecPublicKey = ecdsaRecover (
335+ const ecPublicKey = recoverPublicKey (
336+ Buffer . from ( hashedMessage . substring ( 2 ) , 'hex' ) ,
306337 Buffer . from ( signature . substring ( 2 , V_INDEX ) , 'hex' ) ,
307338 parseInt ( v , 16 ) - 27 ,
308- Buffer . from ( hashedMessage . substring ( 2 ) , 'hex' ) ,
309339 false ,
310340 ) ;
311341
@@ -360,29 +390,20 @@ const uuidV4 = (): string => {
360390 * > "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0"
361391 * ```
362392 */
363- export const privateKeyToAddress = ( privateKey : string | Buffer ) : string => {
364- if ( ! ( isString ( privateKey ) || isBuffer ( privateKey ) ) ) {
365- throw new InvalidPrivateKeyError ( ) ;
366- }
393+ export const privateKeyToAddress = ( privateKey : Bytes ) : string => {
394+ const privateKeyBuffer = parseAndValidatePrivateKey ( privateKey ) ;
367395
368- const stringPrivateKey = Buffer . isBuffer ( privateKey )
369- ? Buffer . from ( privateKey ) . toString ( 'hex' )
370- : privateKey ;
396+ // Get public key from private key in compressed format
397+ const publicKey = getPublicKey ( privateKeyBuffer ) ;
371398
372- const stringPrivateKeyNoPrefix = stringPrivateKey . startsWith ( '0x' )
373- ? stringPrivateKey . slice ( 2 )
374- : stringPrivateKey ;
375-
376- if ( ! isHexString32Bytes ( stringPrivateKeyNoPrefix , false ) ) {
377- throw new PrivateKeyLengthError ( ) ;
378- }
399+ // Uncompressed ECDSA public key contains the prefix `0x04` which is not used in the Ethereum public key
400+ const publicKeyHash = sha3Raw ( publicKey . slice ( 1 ) ) ;
379401
380- const publicKey = getPublicKey ( stringPrivateKeyNoPrefix ) ;
402+ // The hash is returned as 256 bits (32 bytes) or 64 hex characters
403+ // To get the address, take the last 20 bytes of the public hash
404+ const address = publicKeyHash . slice ( - 40 ) ;
381405
382- const publicKeyString = `0x${ publicKey . slice ( 2 ) } ` ;
383- const publicHash = sha3Raw ( publicKeyString ) ;
384- const publicHashHex = bytesToHex ( publicHash ) ;
385- return toChecksumAddress ( publicHashHex . slice ( - 40 ) ) ; // To get the address, take the last 20 bytes of the public hash
406+ return toChecksumAddress ( `0x${ address } ` ) ;
386407} ;
387408
388409/**
@@ -460,21 +481,11 @@ export const privateKeyToAddress = (privateKey: string | Buffer): string => {
460481 *```
461482 */
462483export const encrypt = async (
463- privateKey : HexString ,
484+ privateKey : Bytes ,
464485 password : string | Buffer ,
465486 options ?: CipherOptions ,
466487) : Promise < KeyStore > => {
467- if ( ! ( isString ( privateKey ) || isBuffer ( privateKey ) ) ) {
468- throw new InvalidPrivateKeyError ( ) ;
469- }
470-
471- const stringPrivateKey = Buffer . isBuffer ( privateKey )
472- ? Buffer . from ( privateKey ) . toString ( 'hex' )
473- : privateKey ;
474-
475- if ( ! isHexString32Bytes ( stringPrivateKey ) ) {
476- throw new PrivateKeyLengthError ( ) ;
477- }
488+ const privateKeyBuffer = parseAndValidatePrivateKey ( privateKey ) ;
478489
479490 // if given salt or iv is a string, convert it to a Uint8Array
480491 let salt ;
@@ -546,10 +557,8 @@ export const encrypt = async (
546557 throw new InvalidKdfError ( ) ;
547558 }
548559
549- const cipherKey = Buffer . from ( stringPrivateKey . replace ( '0x' , '' ) , 'hex' ) ;
550-
551560 const cipher = await createCipheriv (
552- cipherKey ,
561+ privateKeyBuffer ,
553562 Buffer . from ( derivedKey . slice ( 0 , 16 ) ) ,
554563 initializationVector ,
555564 'aes-128-ctr' ,
@@ -562,7 +571,7 @@ export const encrypt = async (
562571 return {
563572 version : 3 ,
564573 id : uuidV4 ( ) ,
565- address : privateKeyToAddress ( stringPrivateKey ) . toLowerCase ( ) . replace ( '0x' , '' ) ,
574+ address : privateKeyToAddress ( privateKeyBuffer ) . toLowerCase ( ) . replace ( '0x' , '' ) ,
566575 crypto : {
567576 ciphertext,
568577 cipherparams : {
@@ -596,19 +605,19 @@ export const encrypt = async (
596605 * }
597606 * ```
598607 */
599- export const privateKeyToAccount = ( privateKey : string | Buffer ) : Web3Account => {
600- const pKey = Buffer . isBuffer ( privateKey ) ? Buffer . from ( privateKey ) . toString ( 'hex' ) : privateKey ;
608+ export const privateKeyToAccount = ( privateKey : Bytes ) : Web3Account => {
609+ const privateKeyBuffer = parseAndValidatePrivateKey ( privateKey ) ;
601610
602611 return {
603- address : privateKeyToAddress ( pKey ) ,
604- privateKey : pKey ,
612+ address : privateKeyToAddress ( privateKeyBuffer ) ,
613+ privateKey : bytesToHex ( privateKeyBuffer ) ,
605614 signTransaction : ( _tx : Record < string , unknown > ) => {
606615 throw new SignerError ( 'Do not have network access to sign the transaction' ) ;
607616 } ,
608617 sign : ( data : Record < string , unknown > | string ) =>
609- sign ( typeof data === 'string' ? data : JSON . stringify ( data ) , pKey ) ,
618+ sign ( typeof data === 'string' ? data : JSON . stringify ( data ) , privateKeyBuffer ) ,
610619 encrypt : async ( password : string , options ?: Record < string , unknown > ) => {
611- const data = await encrypt ( pKey , password , options ) ;
620+ const data = await encrypt ( privateKeyBuffer , password , options ) ;
612621
613622 return JSON . stringify ( data ) ;
614623 } ,
0 commit comments