/
ECKeyPair.ts
91 lines (79 loc) · 3.49 KB
/
ECKeyPair.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
import * as Crypto from "crypto";
import * as _ from "lodash";
import * as Long from "long";
import { privateKeyVerify, publicKeyCreate, publicKeyTweakMul, sign } from "secp256k1";
import { assertThrow, Utils } from "../utils/Utils";
import { Address } from "./Address";
import { Passphrase } from "./Passphrase";
import { Wif } from "./Wif";
export class ECKeyPair {
public static parseWif(base58: string): ECKeyPair {
let data = Buffer.from(Utils.Base58.decode(base58));
assertThrow(data.length > 4, () => "input too short");
const checksum = data.slice(data.length - 4, data.length);
data = data.slice(0, data.length - 4);
assertThrow(data[0] === this.VERSION, () => `${data[0]} is not a valid private key version byte`);
const actualChecksum = Utils.hashTwice256(data).slice(0, 4);
assertThrow(_.isEqual(actualChecksum, checksum), () => "checksum not valid");
// drop version byte
data = data.slice(1, data.length);
// check compressed byte and drop if true
data = data.length === 33 && data[32] === 1 ? data.slice(0, data.length - 1) : data;
return new ECKeyPair(data);
}
public static generate(): ECKeyPair {
let random: Buffer;
do {
random = Crypto.randomBytes(32);
} while (!privateKeyVerify(random));
return new ECKeyPair(random);
}
public static generateFromPhrase(phrase: Passphrase | string, sequence: number = 0): ECKeyPair {
let random: Buffer;
do {
random = Utils.hash256(Utils.hash512(Buffer.from(`${phrase.toString()} ${sequence}`)));
} while (!privateKeyVerify(random));
return new ECKeyPair(random);
}
/*
https://github.com/steemit/steem/issues/1944
bool public_key::is_canonical( const compact_signature& c ) {
return !(c.data[1] & 0x80)
&& !(c.data[1] == 0 && !(c.data[2] & 0x80))
&& !(c.data[33] & 0x80)
&& !(c.data[33] == 0 && !(c.data[34] & 0x80));
}
*/
public static checkCanonicalSignature(sigData: Buffer): boolean {
return sigData[1] < 0x80 && !(sigData[1] === 0 && sigData[2] < 0x80)
&& sigData[33] < 0x80 && !(sigData[33] === 0 && sigData[34] < 0x80);
}
private static VERSION: number = 0x80;
private static COMPRESSED: number = 4;
private static COMPACT: number = 27;
public readonly privateKey: Buffer;
public readonly publicKey: Buffer;
/**
* the public key is always in a compressed format in DCore
*/
private constructor(privateKey: Buffer) {
this.privateKey = privateKey;
this.publicKey = publicKeyCreate(this.privateKey, true);
}
public get publicAddress(): Address {
return new Address(this.publicKey);
}
public get privateWif(): Wif {
return new Wif(this.privateKey, ECKeyPair.VERSION);
}
public sign(data: Buffer): string | undefined {
const sig = sign(Utils.hash256(data), this.privateKey);
const head = Buffer.alloc(1, sig.recovery + ECKeyPair.COMPRESSED + ECKeyPair.COMPACT);
const sigData = Buffer.concat([head, sig.signature]);
return ECKeyPair.checkCanonicalSignature(sigData) ? sigData.toString("hex") : undefined;
}
public secret(recipient: Address, nonce: Long): Buffer {
const key = publicKeyTweakMul(recipient.publicKey, this.privateKey, true);
return Utils.hash512(Buffer.from(nonce.toString() + Utils.hash512(key.slice(1)).toString("hex")));
}
}