-
Notifications
You must be signed in to change notification settings - Fork 59
/
crypto.ts
216 lines (197 loc) · 6.62 KB
/
crypto.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import nacl, { SignKeyPair } from 'tweetnacl';
// js extension is required for mjs build, not importing the whole package to reduce bundle size
// eslint-disable-next-line import/extensions
import { blake2b } from 'blakejs/blake2b.js';
import { encode as varuintEncode } from 'varuint-bitcoin';
import { concatBuffers, isItemOfArray } from './other';
import {
decode, encode, Encoded, Encoding,
} from './encoder';
import { ArgumentError } from './errors';
/**
* Generate address from secret key
* @param secret - Private key as hex string
* @returns Public key encoded as address
*/
export function getAddressFromPriv(secret: string | Uint8Array): Encoded.AccountAddress {
const secretBuffer = typeof secret === 'string' ? Buffer.from(secret, 'hex') : secret;
const keys = nacl.sign.keyPair.fromSecretKey(secretBuffer);
return encode(keys.publicKey, Encoding.AccountAddress);
}
/**
* Check if address is valid
* @param maybeAddress - Address to check
*/
export function isAddressValid(maybeAddress: string): maybeAddress is Encoded.AccountAddress;
/**
* Check if data is encoded in one of provided encodings
* @param maybeEncoded - Data to check
* @param encodings - Rest parameters with encodings to check against
*/
export function isAddressValid<E extends Encoding>(
maybeEncoded: string,
...encodings: E[]
): maybeEncoded is Encoded.Generic<E>;
export function isAddressValid(maybeEncoded: string, ...encodings: Encoding[]): boolean {
if (encodings.length === 0) encodings = [Encoding.AccountAddress];
try {
decode(maybeEncoded as Encoded.Any);
const encoding = maybeEncoded.split('_')[0];
if (!isItemOfArray(encoding, encodings)) {
throw new ArgumentError(
'Encoded string type',
encodings.length > 1 ? `one of ${encodings.join(', ')}` : encodings[0],
encoding,
);
}
return true;
} catch (error) {
return false;
}
}
/**
* Generate a random salt (positive integer)
* @returns random salt
*/
export function genSalt(): number {
const [random] = new BigUint64Array(nacl.randomBytes(8).buffer);
return Number(random % BigInt(Number.MAX_SAFE_INTEGER));
}
/**
* Converts a positive integer to the smallest possible
* representation in a binary digit representation
* @param value - Value to encode
* @returns Encoded number
*/
export function encodeUnsigned(value: number): Buffer {
const binary = Buffer.allocUnsafe(4);
binary.writeUInt32BE(value);
return binary.slice(binary.findIndex((i) => i !== 0));
}
/**
* Calculate 256bits Blake2b hash of `input`
* @param input - Data to hash
* @returns Hash
*/
export function hash(input: string | Uint8Array): Buffer {
return Buffer.from(blake2b(input, undefined, 32)); // 256 bits
}
// Todo Duplicated in tx builder. remove
/**
* Compute contract address
* @category contract
* @param owner - Address of contract owner
* @param nonce - Round when contract was created
* @returns Contract address
*/
export function encodeContractAddress(
owner: Encoded.AccountAddress,
nonce: number,
): Encoded.ContractAddress {
const publicKey = decode(owner);
const binary = concatBuffers([publicKey, encodeUnsigned(nonce)]);
return encode(hash(binary), Encoding.ContractAddress);
}
// KEY-PAIR HELPERS
/**
* Generate keyPair from secret key
* @param secret - secret key
* @returns Object with Private(privateKey) and Public(publicKey) keys
*/
export function generateKeyPairFromSecret(secret: Uint8Array): SignKeyPair {
return nacl.sign.keyPair.fromSecretKey(secret);
}
/**
* Generate a random ED25519 keypair
* @param raw - Whether to return raw (binary) keys
* @returns Key pair
*/
export function generateKeyPair(raw: true): { publicKey: Buffer; secretKey: Buffer };
export function generateKeyPair(raw?: false): {
publicKey: Encoded.AccountAddress; secretKey: string;
};
export function generateKeyPair(raw = false): {
publicKey: Encoded.AccountAddress | Buffer;
secretKey: string | Buffer;
} {
const keyPair = nacl.sign.keyPair();
const publicBuffer = Buffer.from(keyPair.publicKey);
const secretBuffer = Buffer.from(keyPair.secretKey);
if (raw) {
return {
publicKey: publicBuffer,
secretKey: secretBuffer,
};
}
return {
publicKey: encode(publicBuffer, Encoding.AccountAddress),
secretKey: secretBuffer.toString('hex'),
};
}
// SIGNATURES
/**
* Generate signature
* @param data - Data to sign
* @param privateKey - Key to sign with
* @returns Signature
*/
export function sign(data: string | Uint8Array, privateKey: string | Uint8Array): Uint8Array {
return nacl.sign.detached(Buffer.from(data), Buffer.from(privateKey));
}
/**
* Verify that signature was signed by public key
* @param data - Data that was signed
* @param signature - Signature of data
* @param address - Address to verify against
* @returns is data was signed by address
*/
export function verify(
data: Uint8Array,
signature: Uint8Array,
address: Encoded.AccountAddress,
): boolean {
return nacl.sign.detached.verify(data, signature, decode(address));
}
const messagePrefix = Buffer.from('aeternity Signed Message:\n', 'utf8');
export const messagePrefixLength = varuintEncode(messagePrefix.length);
// TODO: consider rename to hashMessage
export function messageToHash(message: string): Buffer {
const msg = Buffer.from(message, 'utf8');
return hash(concatBuffers([messagePrefixLength, messagePrefix, varuintEncode(msg.length), msg]));
}
export function signMessage(message: string, privateKey: string | Buffer): Uint8Array {
return sign(messageToHash(message), privateKey);
}
/**
* Verify that message was signed by address
* @param message - Message that was signed
* @param signature - Signature of message
* @param address - Address to verify against
* @returns is data was signed by address
*/
// TODO: deprecate in favour of `verify(messageToHash(message), ...`, also the name is confusing
// it should contain "signature"
export function verifyMessage(
message: string,
signature: Uint8Array,
address: Encoded.AccountAddress,
): boolean {
return verify(messageToHash(message), signature, address);
}
/**
* Check key pair for validity
*
* Signs a message, and then verifies that signature
* @param privateKey - Private key to verify
* @param publicKey - Public key to verify as hex string
* @returns Valid?
*/
export function isValidKeypair(
privateKey: string | Uint8Array,
publicKey: string | Uint8Array,
): boolean {
const message = Buffer.from('TheMessage');
const signature = sign(message, privateKey);
const publicKeyBuffer = typeof publicKey === 'string' ? Buffer.from(publicKey, 'hex') : publicKey;
return verify(message, signature, encode(publicKeyBuffer, Encoding.AccountAddress));
}