This repository has been archived by the owner on May 14, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
S256Point.ts
195 lines (178 loc) · 5.54 KB
/
S256Point.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
import { Point } from "./Point";
import { S256Field } from "./S256Field";
import { mod, pow } from "../util/BigIntMath";
import { Signature } from "./Signature";
import { bigToBuf, bigFromBuf } from "../util/BigIntUtil";
import { hash160 } from "../util/Hash160";
import { p2pkhAddress } from "../util/Address";
/**
* Defines a point on the secp256k1 curve by specifying the a and b values.
* This allows us to initialize a point on the secp256k1 curve easily without
* needing to specify a and b every time. It also restricts values to the
* field defined in secp256k1.
*
* This class also specifies the order N, and restricts scalar multiplication
* to the order N.
*/
export class S256Point extends Point<S256Field> {
/**
* `a` value defined for secp256k1 for equation `y**2 = x**3 + ax + b`
*/
public static a = 0n;
/**
* `b` value defined for secp256k1 for equation `y**2 = x**3 + ax + b`
*/
public static b = 7n;
/**
* `N` defines the order used by secp256k1
*/
public static N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); // prettier-ignore
/**
* Generator point for secp256k1
*/
public static G = new S256Point(
BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"),
); // prettier-ignore
/**
* Infinity value
*/
// tslint:disable-next-line: variable-name
public static Infinity = new S256Point(undefined, undefined);
/**
* Parses an SEC public key from either uncompressed format or compressed format
* @param buf
*/
public static parse(buf: Buffer): S256Point {
// uncompressed point
if (buf[0] === 0x04) {
const x = bigFromBuf(buf.slice(1, 33));
const y = bigFromBuf(buf.slice(33, 65));
return new S256Point(x, y);
}
// compressed format
else {
// x is easy to get
const x = new S256Field(bigFromBuf(buf.slice(1)));
// right side of equation y^2 = x^3 +7
const right = x.pow(3n).add(new S256Field(S256Point.b)); // prettier-ignore
// solve the left side of the equation, this will result in two values
// for positive and negative: y and p-y
const beta = new S256Field(right.num).sqrt();
let evenBeta: S256Field;
let oddBeta: S256Field;
if (beta.num % 2n === 0n) {
evenBeta = beta;
oddBeta = new S256Field(S256Field.P - beta.num);
} else {
evenBeta = new S256Field(S256Field.P - beta.num);
oddBeta = beta;
}
const isEven = buf[0] === 0x02;
if (isEven) {
return new S256Point(x.num, evenBeta.num);
} else {
return new S256Point(x.num, oddBeta.num);
}
}
}
constructor(x: bigint, y: bigint) {
super(
x ? new S256Field(x) : undefined,
y ? new S256Field(y) : undefined,
new S256Field(S256Point.a),
new S256Field(S256Point.b)
);
}
/**
*
* @param scalar
*/
public smul(scalar: bigint) {
scalar = mod(scalar, S256Point.N);
const point = super.smul(scalar);
return new S256Point(
point.x ? point.x.num : undefined,
point.y ? point.y.num : undefined
);
}
/**
* Verifies a signature.
*
* The verification process is as follows:
* We are provided (r,s) as the signature and z as the hash
* of the thing being signed, and P as the public key (public point) of the signer.
*
* This calculates:
* `u = z/s`
* `v = r/s`
*
* We then calculate `uG + vP = R`
* If `R's` `x` coordinate equals `r`, then signature is valid!
*
* Implementation notes:
* - `s_inv` is calculated using Fermat's Little Theorem to calculate `1/s` since `n` is prime.
* - `uG + vP = (r,y)` but we only care about r.
*
* @param z hash of information that was signed
* @param sig signature r,s
*/
public verify(z: bigint, sig: Signature): boolean {
const sinv = pow(sig.s, S256Point.N - 2n, S256Point.N);
const u = mod(z * sinv, S256Point.N);
const v = mod(sig.r * sinv, S256Point.N);
const total = S256Point.G.smul(u).add(this.smul(v));
return sig.r === total.x.num;
}
/**
* Encodes the point as SEC (Standards for Efficiency Cryptography) point.
* All values are encoded as big-endiant.
*
* Uncompressed format:
* ```
* 0x04 + x + y
* ```
*
* Compressed format:
* ```
* 0x02 + x => when y is even
* 0x03 + x => when y is odd
* ```
*/
public sec(compressed: boolean = false): Buffer {
if (compressed) {
const prefix = this.y.num % 2n === 0n ? 2 : 3;
return Buffer.concat([
Buffer.from([prefix]),
bigToBuf(this.x.num)
]); // prettier-ignore
} else {
return Buffer.concat([
Buffer.from([0x04]),
bigToBuf(this.x.num),
bigToBuf(this.y.num),
]);
}
}
/**
* Performs the hash160 of the SEC encoded point. The SEC
* point can be either compressed or uncompressed.
* @param compressed default is true
*/
public hash160(compressed: boolean = true): Buffer {
return hash160(this.sec(compressed));
}
/**
* Generates the base58check encoded address for a P2PKH address.
* It accepts the hash of an SEC encoded point. The hash can either
* be from the compressed or uncompressed value.
*
* Testnet uses a prefix of 0x6f
* Mainnet uses a prefix of 0x00
*
* @param testnet default is false
*/
public address(compressed: boolean = true, testnet: boolean = false): string {
return p2pkhAddress(this.hash160(compressed), testnet);
}
}