2019-03-03 15:07:49 +01:00
|
|
|
import { Network } from './networks';
|
|
|
|
import * as NETWORKS from './networks';
|
|
|
|
import * as types from './types';
|
|
|
|
const ecc = require('tiny-secp256k1');
|
|
|
|
const randomBytes = require('randombytes');
|
|
|
|
const typeforce = require('typeforce');
|
|
|
|
const wif = require('wif');
|
|
|
|
|
|
|
|
const isOptions = typeforce.maybe(
|
|
|
|
typeforce.compile({
|
|
|
|
compressed: types.maybe(types.Boolean),
|
|
|
|
network: types.maybe(types.Network),
|
|
|
|
}),
|
|
|
|
);
|
2015-08-19 07:12:55 +02:00
|
|
|
|
2018-12-28 03:56:03 +01:00
|
|
|
interface ECPairOptions {
|
2019-03-03 15:07:49 +01:00
|
|
|
compressed?: boolean;
|
|
|
|
network?: Network;
|
2019-04-11 08:55:33 +02:00
|
|
|
rng?(arg0: number): Buffer;
|
2018-05-22 08:33:43 +02:00
|
|
|
}
|
2015-08-11 09:01:47 +02:00
|
|
|
|
2019-06-14 08:25:07 +02:00
|
|
|
export interface Signer {
|
|
|
|
publicKey: Buffer;
|
2019-08-06 11:45:02 +02:00
|
|
|
network?: any;
|
2019-06-14 08:25:07 +02:00
|
|
|
sign(hash: Buffer, lowR?: boolean): Buffer;
|
|
|
|
getPublicKey?(): Buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface SignerAsync {
|
|
|
|
publicKey: Buffer;
|
2019-08-06 11:45:02 +02:00
|
|
|
network?: any;
|
2019-06-14 08:25:07 +02:00
|
|
|
sign(hash: Buffer, lowR?: boolean): Promise<Buffer>;
|
|
|
|
getPublicKey?(): Buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ECPairInterface extends Signer {
|
2019-03-03 15:07:49 +01:00
|
|
|
compressed: boolean;
|
|
|
|
network: Network;
|
2019-07-25 11:15:11 +02:00
|
|
|
lowR: boolean;
|
2019-03-03 15:07:49 +01:00
|
|
|
privateKey?: Buffer;
|
|
|
|
toWIF(): string;
|
2019-04-23 08:10:01 +02:00
|
|
|
verify(hash: Buffer, signature: Buffer): boolean;
|
2018-12-28 03:56:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class ECPair implements ECPairInterface {
|
2019-03-03 15:07:49 +01:00
|
|
|
compressed: boolean;
|
|
|
|
network: Network;
|
2019-07-25 11:15:11 +02:00
|
|
|
lowR: boolean;
|
2019-03-03 15:07:49 +01:00
|
|
|
|
2019-03-20 07:25:48 +01:00
|
|
|
constructor(
|
|
|
|
private __D?: Buffer,
|
|
|
|
private __Q?: Buffer,
|
|
|
|
options?: ECPairOptions,
|
|
|
|
) {
|
2019-07-25 11:15:11 +02:00
|
|
|
this.lowR = false;
|
2019-03-03 15:07:49 +01:00
|
|
|
if (options === undefined) options = {};
|
|
|
|
this.compressed =
|
|
|
|
options.compressed === undefined ? true : options.compressed;
|
|
|
|
this.network = options.network || NETWORKS.bitcoin;
|
|
|
|
|
2019-03-20 07:25:48 +01:00
|
|
|
if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed);
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
get privateKey(): Buffer | undefined {
|
2019-03-07 04:06:12 +01:00
|
|
|
return this.__D;
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
2015-03-19 03:25:41 +01:00
|
|
|
|
2019-05-16 09:29:23 +02:00
|
|
|
get publicKey(): Buffer {
|
|
|
|
if (!this.__Q)
|
|
|
|
this.__Q = ecc.pointFromScalar(this.__D, this.compressed) as Buffer;
|
2019-03-03 15:07:49 +01:00
|
|
|
return this.__Q;
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
2015-03-19 03:25:41 +01:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
toWIF(): string {
|
2019-03-07 04:06:12 +01:00
|
|
|
if (!this.__D) throw new Error('Missing private key');
|
|
|
|
return wif.encode(this.network.wif, this.__D, this.compressed);
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2019-07-25 11:15:11 +02:00
|
|
|
sign(hash: Buffer, lowR?: boolean): Buffer {
|
2019-03-07 04:06:12 +01:00
|
|
|
if (!this.__D) throw new Error('Missing private key');
|
2019-07-25 11:15:11 +02:00
|
|
|
if (lowR === undefined) lowR = this.lowR;
|
2019-04-12 10:44:55 +02:00
|
|
|
if (lowR === false) {
|
|
|
|
return ecc.sign(hash, this.__D);
|
|
|
|
} else {
|
|
|
|
let sig = ecc.sign(hash, this.__D);
|
|
|
|
const extraData = Buffer.alloc(32, 0);
|
|
|
|
let counter = 0;
|
|
|
|
// if first try is lowR, skip the loop
|
|
|
|
// for second try and on, add extra entropy counting up
|
|
|
|
while (sig[0] > 0x7f) {
|
|
|
|
counter++;
|
|
|
|
extraData.writeUIntLE(counter, 0, 6);
|
|
|
|
sig = ecc.signWithEntropy(hash, this.__D, extraData);
|
|
|
|
}
|
|
|
|
return sig;
|
|
|
|
}
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
|
|
|
|
2019-04-23 08:10:01 +02:00
|
|
|
verify(hash: Buffer, signature: Buffer): boolean {
|
2019-03-03 15:07:49 +01:00
|
|
|
return ecc.verify(hash, this.publicKey, signature);
|
2018-12-28 02:35:28 +01:00
|
|
|
}
|
2014-10-17 04:31:01 +02:00
|
|
|
}
|
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair {
|
|
|
|
typeforce(types.Buffer256bit, buffer);
|
|
|
|
if (!ecc.isPrivate(buffer))
|
|
|
|
throw new TypeError('Private key not in range [1, n)');
|
|
|
|
typeforce(isOptions, options);
|
2018-05-22 08:33:43 +02:00
|
|
|
|
2019-05-16 09:29:23 +02:00
|
|
|
return new ECPair(buffer, undefined, options);
|
2018-05-22 08:33:43 +02:00
|
|
|
}
|
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair {
|
|
|
|
typeforce(ecc.isPoint, buffer);
|
|
|
|
typeforce(isOptions, options);
|
2019-05-16 09:29:23 +02:00
|
|
|
return new ECPair(undefined, buffer, options);
|
2018-05-22 08:33:43 +02:00
|
|
|
}
|
|
|
|
|
2019-03-07 04:06:12 +01:00
|
|
|
function fromWIF(wifString: string, network?: Network | Network[]): ECPair {
|
|
|
|
const decoded = wif.decode(wifString);
|
2019-03-03 15:07:49 +01:00
|
|
|
const version = decoded.version;
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2017-01-06 03:41:51 +01:00
|
|
|
// list of networks?
|
2015-08-20 12:16:57 +02:00
|
|
|
if (types.Array(network)) {
|
2019-03-07 04:06:12 +01:00
|
|
|
network = (network as Network[])
|
|
|
|
.filter((x: Network) => {
|
2019-03-03 15:07:49 +01:00
|
|
|
return version === x.wif;
|
|
|
|
})
|
2019-03-07 04:06:12 +01:00
|
|
|
.pop() as Network;
|
2016-02-25 03:48:29 +01:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!network) throw new Error('Unknown network version');
|
2016-04-27 09:05:33 +02:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
// otherwise, assume a network object (or default to bitcoin)
|
2016-04-27 10:04:35 +02:00
|
|
|
} else {
|
2019-03-03 15:07:49 +01:00
|
|
|
network = network || NETWORKS.bitcoin;
|
2016-04-27 10:04:35 +02:00
|
|
|
|
2019-03-07 04:06:12 +01:00
|
|
|
if (version !== (network as Network).wif)
|
2019-03-03 15:07:49 +01:00
|
|
|
throw new Error('Invalid network version');
|
2015-07-28 08:42:57 +02:00
|
|
|
}
|
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
return fromPrivateKey(decoded.privateKey, {
|
2015-08-20 12:16:57 +02:00
|
|
|
compressed: decoded.compressed,
|
2019-03-07 04:06:12 +01:00
|
|
|
network: network as Network,
|
2019-03-03 15:07:49 +01:00
|
|
|
});
|
2014-10-17 04:31:01 +02:00
|
|
|
}
|
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
function makeRandom(options?: ECPairOptions): ECPair {
|
|
|
|
typeforce(isOptions, options);
|
|
|
|
if (options === undefined) options = {};
|
|
|
|
const rng = options.rng || randomBytes;
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
let d;
|
2015-08-21 08:46:18 +02:00
|
|
|
do {
|
2019-03-03 15:07:49 +01:00
|
|
|
d = rng(32);
|
|
|
|
typeforce(types.Buffer256bit, d);
|
|
|
|
} while (!ecc.isPrivate(d));
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
return fromPrivateKey(d, options);
|
2015-09-21 09:37:21 +02:00
|
|
|
}
|
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF };
|