1 | const { keccak256, ecdsaSign, ecdsaRecover, publicKeyToAddress } = require('./util/sign');
|
2 | const format = require('./util/format');
|
3 |
|
4 | class Message {
|
5 | /**
|
6 | * Signs the hash with the privateKey.
|
7 | *
|
8 | * @param privateKey {string|Buffer}
|
9 | * @param messageHash {string|Buffer}
|
10 | * @return {string} The signature as hex string.
|
11 | *
|
12 | * @example
|
13 | * > Message.sign(
|
14 | '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', // privateKey
|
15 | '0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba',
|
16 | )
|
17 | "0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01"
|
18 | */
|
19 | static sign(privateKey, messageHash) {
|
20 | const { r, s, v } = ecdsaSign(format.hexBuffer(messageHash), format.hexBuffer(privateKey));
|
21 | const buffer = Buffer.concat([r, s, format.hexBuffer(v)]);
|
22 | return format.hex(buffer);
|
23 | }
|
24 |
|
25 | /**
|
26 | * Recovers the signers publicKey from the signature.
|
27 | *
|
28 | * @param signature {string|Buffer}
|
29 | * @param messageHash {string|Buffer}
|
30 | * @return {string} The publicKey as hex string.
|
31 | *
|
32 | * @example
|
33 | * > Message.recover(
|
34 | '0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01',
|
35 | '0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba',
|
36 | )
|
37 | "0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559"
|
38 | */
|
39 | static recover(signature, messageHash) {
|
40 | const signatureBuffer = format.hexBuffer(signature);
|
41 | const r = signatureBuffer.slice(0, 32);
|
42 | const s = signatureBuffer.slice(32, 64);
|
43 | const v = signatureBuffer[64];
|
44 | const buffer = ecdsaRecover(format.hexBuffer(messageHash), { r, s, v });
|
45 | return format.publicKey(buffer);
|
46 | }
|
47 |
|
48 | /**
|
49 | * @param message {string}
|
50 | * @return {Message}
|
51 | *
|
52 | * @example
|
53 | * > msg = new Message('Hello World');
|
54 | Message {
|
55 | message: 'Hello World',
|
56 | }
|
57 | * > msg.sign('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef');
|
58 | Message {
|
59 | message: 'Hello World',
|
60 | signature: '0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01'
|
61 | }
|
62 | * > msg.signature
|
63 | "0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f01"
|
64 | * > msg.hash
|
65 | "0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba"
|
66 | * > msg.from
|
67 | "0x1cad0b19bb29d4674531d6f115237e16afce377c"
|
68 | * > msg.r
|
69 | "0x6e913e2b76459f19ebd269b82b51a70e912e909b2f5c002312efc27bcc280f3c"
|
70 | * > msg.s
|
71 | "0x29134d382aad0dbd3f0ccc9f0eb8f1dbe3f90141d81574ebb6504156b0d7b95f"
|
72 | * > msg.v
|
73 | 1
|
74 | */
|
75 | constructor(message) {
|
76 | this.message = message;
|
77 | }
|
78 |
|
79 | /**
|
80 | * Getter of message hash include signature.
|
81 | *
|
82 | * > Note: calculate every time.
|
83 | *
|
84 | * @return {string}
|
85 | */
|
86 | get hash() {
|
87 | return format.hex(keccak256(Buffer.from(this.message)));
|
88 | }
|
89 |
|
90 | /**
|
91 | * Getter of sender address.
|
92 | *
|
93 | * > Note: calculate every time.
|
94 | *
|
95 | * @return {string|undefined} If ECDSA recover success return address, else return undefined.
|
96 | */
|
97 | get from() {
|
98 | try {
|
99 | const publicKey = Message.recover(this.signature, this.hash);
|
100 | return format.address(publicKeyToAddress(format.hexBuffer(publicKey)));
|
101 | } catch (e) {
|
102 | return undefined;
|
103 | }
|
104 | }
|
105 |
|
106 | /**
|
107 | * Sign message and set 'r','s','v' and 'hash'.
|
108 | *
|
109 | * @param privateKey {string} - Private key hex string.
|
110 | * @return {Message}
|
111 | */
|
112 | sign(privateKey) {
|
113 | this.signature = Message.sign(privateKey, this.hash);
|
114 | return this;
|
115 | }
|
116 |
|
117 | get r() {
|
118 | try {
|
119 | return this.signature.slice(0, 2 + 64);
|
120 | } catch (e) {
|
121 | return undefined;
|
122 | }
|
123 | }
|
124 |
|
125 | get s() {
|
126 | try {
|
127 | return `0x${this.signature.slice(2 + 64, 2 + 128)}`;
|
128 | } catch (e) {
|
129 | return undefined;
|
130 | }
|
131 | }
|
132 |
|
133 | get v() {
|
134 | try {
|
135 | return parseInt(this.signature.slice(2 + 128, 2 + 130), 16);
|
136 | } catch (e) {
|
137 | return undefined;
|
138 | }
|
139 | }
|
140 | }
|
141 |
|
142 | module.exports = Message;
|