@btc-vision/btc-runtime 1.4.5 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/runtime/buffer/BytesReader.ts +1 -1
- package/runtime/contracts/DeployableOP_20.ts +56 -47
- package/runtime/index.ts +0 -1
- package/runtime/math/bytes.ts +22 -8
- package/runtime/secp256k1/ECPoint.ts +103 -75
- package/runtime/storage/Serializable.ts +13 -10
- package/runtime/storage/arrays/StoredAddressArray.ts +16 -58
- package/runtime/storage/arrays/StoredBooleanArray.ts +22 -66
- package/runtime/storage/arrays/StoredU128Array.ts +17 -63
- package/runtime/storage/arrays/StoredU16Array.ts +15 -59
- package/runtime/storage/arrays/StoredU256Array.ts +26 -65
- package/runtime/storage/arrays/StoredU32Array.ts +15 -25
- package/runtime/storage/arrays/StoredU8Array.ts +15 -58
- package/runtime/types/Address.ts +1 -2
- package/runtime/types/SafeMath.ts +56 -28
- package/runtime/types/SafeMathI128.ts +8 -0
- package/runtime/math/rnd.ts +0 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btc-vision/btc-runtime",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "Bitcoin Smart Contract Runtime",
|
|
5
5
|
"main": "btc/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@assemblyscript/loader": "^0.27.30",
|
|
46
|
-
"@btc-vision/as-bignum": "^0.0.
|
|
46
|
+
"@btc-vision/as-bignum": "^0.0.4",
|
|
47
47
|
"@eslint/js": "^9.10.0",
|
|
48
48
|
"gulplog": "^2.2.0",
|
|
49
49
|
"mocha": "^10.7.3",
|
|
@@ -262,7 +262,7 @@ export class BytesReader {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
public verifyEnd(size: i32): void {
|
|
265
|
-
if (
|
|
265
|
+
if (size > this.buffer.byteLength) {
|
|
266
266
|
throw new Error(`Expected to read ${size} bytes but read ${this.currentOffset} bytes`);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
@@ -19,6 +19,7 @@ import { OP_NET } from './OP_NET';
|
|
|
19
19
|
import { sha256 } from '../env/global';
|
|
20
20
|
import { ApproveStr, TransferFromStr, TransferStr } from '../shared-libraries/TransferHelper';
|
|
21
21
|
|
|
22
|
+
const nonceMapPointer: u16 = Blockchain.nextPointer;
|
|
22
23
|
const maxSupplyPointer: u16 = Blockchain.nextPointer;
|
|
23
24
|
const decimalsPointer: u16 = Blockchain.nextPointer;
|
|
24
25
|
const namePointer: u16 = Blockchain.nextPointer;
|
|
@@ -36,18 +37,20 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
36
37
|
protected readonly _name: StoredString;
|
|
37
38
|
protected readonly _symbol: StoredString;
|
|
38
39
|
|
|
40
|
+
protected readonly _nonceMap: AddressMemoryMap<u256>;
|
|
41
|
+
|
|
39
42
|
protected constructor(params: OP20InitParameters | null = null) {
|
|
40
43
|
super();
|
|
41
44
|
|
|
45
|
+
// Initialize main storage structures
|
|
42
46
|
this.allowanceMap = new MultiAddressMemoryMap<u256>(allowanceMapPointer, u256.Zero);
|
|
43
47
|
this.balanceOfMap = new AddressMemoryMap<u256>(balanceOfMapPointer, u256.Zero);
|
|
44
48
|
this._totalSupply = new StoredU256(totalSupplyPointer, u256.Zero, u256.Zero);
|
|
45
|
-
|
|
46
49
|
this._maxSupply = new StoredU256(maxSupplyPointer, u256.Zero, u256.Zero);
|
|
47
50
|
this._decimals = new StoredU256(decimalsPointer, u256.Zero, u256.Zero);
|
|
48
|
-
|
|
49
51
|
this._name = new StoredString(namePointer, '');
|
|
50
52
|
this._symbol = new StoredString(symbolPointer, '');
|
|
53
|
+
this._nonceMap = new AddressMemoryMap(nonceMapPointer, u256.Zero);
|
|
51
54
|
|
|
52
55
|
if (params && this._maxSupply.value.isZero()) {
|
|
53
56
|
this.instantiate(params, true);
|
|
@@ -62,25 +65,21 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
62
65
|
|
|
63
66
|
public get maxSupply(): u256 {
|
|
64
67
|
if (!this._maxSupply) throw new Revert('Max supply not set');
|
|
65
|
-
|
|
66
68
|
return this._maxSupply.value;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
public get decimals(): u8 {
|
|
70
72
|
if (!this._decimals) throw new Revert('Decimals not set');
|
|
71
|
-
|
|
72
73
|
return u8(this._decimals.value.toU32());
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
public get name(): string {
|
|
76
77
|
if (!this._name) throw new Revert('Name not set');
|
|
77
|
-
|
|
78
78
|
return this._name.value;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
public get symbol(): string {
|
|
82
82
|
if (!this._symbol) throw new Revert('Symbol not set');
|
|
83
|
-
|
|
84
83
|
return this._symbol.value;
|
|
85
84
|
}
|
|
86
85
|
|
|
@@ -104,52 +103,60 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
104
103
|
/** METHODS */
|
|
105
104
|
public allowance(callData: Calldata): BytesWriter {
|
|
106
105
|
const response = new BytesWriter(U256_BYTE_LENGTH);
|
|
107
|
-
|
|
108
106
|
const resp = this._allowance(callData.readAddress(), callData.readAddress());
|
|
109
107
|
response.writeU256(resp);
|
|
110
|
-
|
|
111
108
|
return response;
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
public approve(callData: Calldata): BytesWriter {
|
|
115
|
-
// Define the owner and spender
|
|
116
112
|
const owner = Blockchain.tx.sender;
|
|
117
113
|
const spender: Address = callData.readAddress();
|
|
118
114
|
const value = callData.readU256();
|
|
119
115
|
|
|
120
|
-
// Response buffer
|
|
121
|
-
const response = new BytesWriter(BOOLEAN_BYTE_LENGTH);
|
|
122
|
-
|
|
123
116
|
const resp = this._approve(owner, spender, value);
|
|
124
|
-
response.writeBoolean(resp);
|
|
125
117
|
|
|
118
|
+
const response = new BytesWriter(BOOLEAN_BYTE_LENGTH);
|
|
119
|
+
response.writeBoolean(resp);
|
|
126
120
|
return response;
|
|
127
121
|
}
|
|
128
122
|
|
|
129
123
|
public approveFrom(callData: Calldata): BytesWriter {
|
|
130
124
|
const response = new BytesWriter(BOOLEAN_BYTE_LENGTH);
|
|
125
|
+
|
|
131
126
|
const owner: Address = Blockchain.tx.origin;
|
|
132
127
|
const spender: Address = callData.readAddress();
|
|
133
128
|
const value: u256 = callData.readU256();
|
|
129
|
+
const nonce: u256 = callData.readU256();
|
|
134
130
|
|
|
135
131
|
const signature = callData.readBytesWithLength();
|
|
136
132
|
if (signature.length !== 64) {
|
|
137
133
|
throw new Revert('Invalid signature length');
|
|
138
134
|
}
|
|
139
135
|
|
|
140
|
-
const resp = this._approveFrom(owner, spender, value, signature);
|
|
136
|
+
const resp = this._approveFrom(owner, spender, value, nonce, signature);
|
|
141
137
|
response.writeBoolean(resp);
|
|
142
138
|
|
|
143
139
|
return response;
|
|
144
140
|
}
|
|
145
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Returns the current nonce for a given owner.
|
|
144
|
+
*/
|
|
145
|
+
public nonceOf(callData: Calldata): BytesWriter {
|
|
146
|
+
const owner = callData.readAddress();
|
|
147
|
+
const currentNonce = this._nonceMap.get(owner);
|
|
148
|
+
|
|
149
|
+
const response = new BytesWriter(32);
|
|
150
|
+
response.writeU256(currentNonce);
|
|
151
|
+
|
|
152
|
+
return response;
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
public balanceOf(callData: Calldata): BytesWriter {
|
|
147
156
|
const response = new BytesWriter(U256_BYTE_LENGTH);
|
|
148
157
|
const address: Address = callData.readAddress();
|
|
149
158
|
const resp = this._balanceOf(address);
|
|
150
|
-
|
|
151
159
|
response.writeU256(resp);
|
|
152
|
-
|
|
153
160
|
return response;
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -157,16 +164,13 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
157
164
|
const response = new BytesWriter(BOOLEAN_BYTE_LENGTH);
|
|
158
165
|
const resp = this._burn(callData.readU256());
|
|
159
166
|
response.writeBoolean(resp);
|
|
160
|
-
|
|
161
167
|
return response;
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
public transfer(callData: Calldata): BytesWriter {
|
|
165
171
|
const response = new BytesWriter(BOOLEAN_BYTE_LENGTH);
|
|
166
172
|
const resp = this._transfer(callData.readAddress(), callData.readU256());
|
|
167
|
-
|
|
168
173
|
response.writeBoolean(resp);
|
|
169
|
-
|
|
170
174
|
return response;
|
|
171
175
|
}
|
|
172
176
|
|
|
@@ -177,9 +181,7 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
177
181
|
callData.readAddress(),
|
|
178
182
|
callData.readU256(),
|
|
179
183
|
);
|
|
180
|
-
|
|
181
184
|
response.writeBoolean(resp);
|
|
182
|
-
|
|
183
185
|
return response;
|
|
184
186
|
}
|
|
185
187
|
|
|
@@ -211,7 +213,7 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
211
213
|
return this.allowance(calldata);
|
|
212
214
|
case encodeSelector(ApproveStr):
|
|
213
215
|
return this.approve(calldata);
|
|
214
|
-
case encodeSelector('approveFrom(address,
|
|
216
|
+
case encodeSelector('approveFrom(address,uint256,uint64,bytes)'):
|
|
215
217
|
return this.approveFrom(calldata);
|
|
216
218
|
case encodeSelector('balanceOf(address)'):
|
|
217
219
|
return this.balanceOf(calldata);
|
|
@@ -221,6 +223,10 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
221
223
|
return this.transfer(calldata);
|
|
222
224
|
case encodeSelector(TransferFromStr):
|
|
223
225
|
return this.transferFrom(calldata);
|
|
226
|
+
|
|
227
|
+
case encodeSelector('nonceOf(address)'):
|
|
228
|
+
return this.nonceOf(calldata);
|
|
229
|
+
|
|
224
230
|
default:
|
|
225
231
|
return super.execute(method, calldata);
|
|
226
232
|
}
|
|
@@ -231,11 +237,16 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
231
237
|
/** REDEFINED METHODS */
|
|
232
238
|
protected _allowance(owner: Address, spender: Address): u256 {
|
|
233
239
|
const senderMap = this.allowanceMap.get(owner);
|
|
234
|
-
|
|
235
240
|
return senderMap.get(spender);
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
protected _approveFrom(
|
|
243
|
+
protected _approveFrom(
|
|
244
|
+
owner: Address,
|
|
245
|
+
spender: Address,
|
|
246
|
+
value: u256,
|
|
247
|
+
nonce: u256,
|
|
248
|
+
signature: Uint8Array,
|
|
249
|
+
): boolean {
|
|
239
250
|
if (owner === Blockchain.DEAD_ADDRESS) {
|
|
240
251
|
throw new Revert('Address can not be dead address');
|
|
241
252
|
}
|
|
@@ -244,22 +255,36 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
244
255
|
throw new Revert('Spender cannot be dead address');
|
|
245
256
|
}
|
|
246
257
|
|
|
247
|
-
//
|
|
248
|
-
const
|
|
258
|
+
// Ensure the nonce matches what we have stored on-chain
|
|
259
|
+
const storedNonce = this._nonceMap.get(owner);
|
|
260
|
+
if (!u256.eq(storedNonce, nonce)) {
|
|
261
|
+
throw new Revert('Invalid nonce (possible replay or out-of-sync)');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Build the hash to match exactly what the user signed, including the nonce
|
|
265
|
+
const writer = new BytesWriter(
|
|
266
|
+
ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH + U256_BYTE_LENGTH,
|
|
267
|
+
);
|
|
268
|
+
|
|
249
269
|
writer.writeAddress(owner);
|
|
250
270
|
writer.writeAddress(spender);
|
|
251
271
|
writer.writeU256(value);
|
|
272
|
+
writer.writeU256(nonce);
|
|
252
273
|
|
|
253
274
|
const hash = sha256(writer.getBuffer());
|
|
254
275
|
if (!Blockchain.verifySchnorrSignature(owner, signature, hash)) {
|
|
255
276
|
throw new Revert('ApproveFrom: Invalid signature');
|
|
256
277
|
}
|
|
257
278
|
|
|
279
|
+
// If valid, increment the nonce so this signature can't be reused
|
|
280
|
+
this._nonceMap.set(owner, SafeMath.add(storedNonce, u256.One));
|
|
281
|
+
|
|
282
|
+
// Update allowance
|
|
258
283
|
const senderMap = this.allowanceMap.get(owner);
|
|
259
284
|
senderMap.set(spender, value);
|
|
260
285
|
|
|
286
|
+
// Emit event
|
|
261
287
|
this.createApproveEvent(owner, spender, value);
|
|
262
|
-
|
|
263
288
|
return true;
|
|
264
289
|
}
|
|
265
290
|
|
|
@@ -267,7 +292,6 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
267
292
|
if (owner === Blockchain.DEAD_ADDRESS) {
|
|
268
293
|
throw new Revert('Address can not be dead address');
|
|
269
294
|
}
|
|
270
|
-
|
|
271
295
|
if (spender === Blockchain.DEAD_ADDRESS) {
|
|
272
296
|
throw new Revert('Spender cannot be dead address');
|
|
273
297
|
}
|
|
@@ -276,14 +300,11 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
276
300
|
senderMap.set(spender, value);
|
|
277
301
|
|
|
278
302
|
this.createApproveEvent(owner, spender, value);
|
|
279
|
-
|
|
280
303
|
return true;
|
|
281
304
|
}
|
|
282
305
|
|
|
283
306
|
protected _balanceOf(owner: Address): u256 {
|
|
284
|
-
|
|
285
|
-
if (!hasAddress) return u256.Zero;
|
|
286
|
-
|
|
307
|
+
if (!this.balanceOfMap.has(owner)) return u256.Zero;
|
|
287
308
|
return this.balanceOfMap.get(owner);
|
|
288
309
|
}
|
|
289
310
|
|
|
@@ -293,7 +314,6 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
293
314
|
}
|
|
294
315
|
|
|
295
316
|
if (onlyDeployer) this.onlyDeployer(Blockchain.tx.sender);
|
|
296
|
-
|
|
297
317
|
if (this._totalSupply.value < value) throw new Revert(`Insufficient total supply.`);
|
|
298
318
|
if (!this.balanceOfMap.has(Blockchain.tx.sender)) throw new Revert('No balance');
|
|
299
319
|
|
|
@@ -303,7 +323,7 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
303
323
|
const newBalance: u256 = SafeMath.sub(balance, value);
|
|
304
324
|
this.balanceOfMap.set(Blockchain.tx.sender, newBalance);
|
|
305
325
|
|
|
306
|
-
// @ts-expect-error
|
|
326
|
+
// @ts-expect-error This is valid AssemblyScript but can trip TS
|
|
307
327
|
this._totalSupply -= value;
|
|
308
328
|
|
|
309
329
|
this.createBurnEvent(value);
|
|
@@ -318,24 +338,20 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
318
338
|
} else {
|
|
319
339
|
const toBalance: u256 = this.balanceOfMap.get(to);
|
|
320
340
|
const newToBalance: u256 = SafeMath.add(toBalance, value);
|
|
321
|
-
|
|
322
341
|
this.balanceOfMap.set(to, newToBalance);
|
|
323
342
|
}
|
|
324
343
|
|
|
325
|
-
// @ts-expect-error
|
|
344
|
+
// @ts-expect-error This is valid AssemblyScript but can trip TS
|
|
326
345
|
this._totalSupply += value;
|
|
327
346
|
|
|
328
347
|
if (this._totalSupply.value > this.maxSupply) throw new Revert('Max supply reached');
|
|
329
|
-
|
|
330
348
|
this.createMintEvent(to, value);
|
|
331
349
|
return true;
|
|
332
350
|
}
|
|
333
351
|
|
|
334
352
|
protected _transfer(to: Address, value: u256): boolean {
|
|
335
353
|
const sender = Blockchain.tx.sender;
|
|
336
|
-
|
|
337
354
|
if (this.isSelf(sender)) throw new Revert('Can not transfer from self account');
|
|
338
|
-
|
|
339
355
|
if (u256.eq(value, u256.Zero)) {
|
|
340
356
|
throw new Revert(`Cannot transfer 0 tokens`);
|
|
341
357
|
}
|
|
@@ -348,10 +364,9 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
348
364
|
|
|
349
365
|
const toBalance: u256 = this.balanceOfMap.get(to);
|
|
350
366
|
const newToBalance: u256 = SafeMath.add(toBalance, value);
|
|
351
|
-
|
|
352
367
|
this.balanceOfMap.set(to, newToBalance);
|
|
353
|
-
this.createTransferEvent(sender, to, value);
|
|
354
368
|
|
|
369
|
+
this.createTransferEvent(sender, to, value);
|
|
355
370
|
return true;
|
|
356
371
|
}
|
|
357
372
|
|
|
@@ -372,12 +387,10 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
372
387
|
} else {
|
|
373
388
|
const toBalance: u256 = this.balanceOfMap.get(to);
|
|
374
389
|
const newToBalance: u256 = SafeMath.add(toBalance, value);
|
|
375
|
-
|
|
376
390
|
this.balanceOfMap.set(to, newToBalance);
|
|
377
391
|
}
|
|
378
392
|
|
|
379
393
|
this.createTransferEvent(from, to, value);
|
|
380
|
-
|
|
381
394
|
return true;
|
|
382
395
|
}
|
|
383
396
|
|
|
@@ -410,25 +423,21 @@ export abstract class DeployableOP_20 extends OP_NET implements IOP_20 {
|
|
|
410
423
|
|
|
411
424
|
protected createBurnEvent(value: u256): void {
|
|
412
425
|
const burnEvent = new BurnEvent(value);
|
|
413
|
-
|
|
414
426
|
this.emitEvent(burnEvent);
|
|
415
427
|
}
|
|
416
428
|
|
|
417
429
|
protected createApproveEvent(owner: Address, spender: Address, value: u256): void {
|
|
418
430
|
const approveEvent = new ApproveEvent(owner, spender, value);
|
|
419
|
-
|
|
420
431
|
this.emitEvent(approveEvent);
|
|
421
432
|
}
|
|
422
433
|
|
|
423
434
|
protected createMintEvent(owner: Address, value: u256): void {
|
|
424
435
|
const mintEvent = new MintEvent(owner, value);
|
|
425
|
-
|
|
426
436
|
this.emitEvent(mintEvent);
|
|
427
437
|
}
|
|
428
438
|
|
|
429
439
|
protected createTransferEvent(from: Address, to: Address, value: u256): void {
|
|
430
440
|
const transferEvent = new TransferEvent(from, to, value);
|
|
431
|
-
|
|
432
441
|
this.emitEvent(transferEvent);
|
|
433
442
|
}
|
|
434
443
|
}
|
package/runtime/index.ts
CHANGED
package/runtime/math/bytes.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { u128, u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
2
|
|
|
3
|
-
export function bytes(
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export function bytes(numbers: u256[]): Uint8Array {
|
|
4
|
+
const len = numbers.length;
|
|
5
|
+
const result = new Uint8Array(32 * len);
|
|
6
|
+
|
|
7
|
+
// Loop through the array in reverse order, so that the last element (index = len-1)
|
|
8
|
+
// becomes the first chunk in the output, and so on.
|
|
9
|
+
for (let i = 0; i < len; i++) {
|
|
10
|
+
// Reverse index to pick elements from the end
|
|
11
|
+
const rev = len - 1 - i;
|
|
12
|
+
const chunk: Uint8Array = numbers[rev].toUint8Array();
|
|
13
|
+
|
|
14
|
+
// Copy this 32-byte chunk into the output at offset i * 32.
|
|
15
|
+
const offset = i * 32;
|
|
16
|
+
for (let b: u32 = 0; b < 32; b++) {
|
|
17
|
+
result[offset + b] = chunk[b];
|
|
9
18
|
}
|
|
10
19
|
}
|
|
11
20
|
|
|
@@ -30,8 +39,13 @@ export function bytes8(number: Uint8Array): u64 {
|
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
export function bytes16(buffer: Uint8Array): u128 {
|
|
33
|
-
//
|
|
34
|
-
if (buffer.length
|
|
42
|
+
// Validate that the buffer is at least 16 bytes:
|
|
43
|
+
if (buffer.length < 16) {
|
|
44
|
+
throw new Error('bytes16: Buffer must be at least 16 bytes long');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If it's larger than 16, slice it down:
|
|
48
|
+
if (buffer.length > 16) {
|
|
35
49
|
buffer = buffer.slice(0, 16);
|
|
36
50
|
}
|
|
37
51
|
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
2
|
import { SafeMath } from '../types/SafeMath';
|
|
3
3
|
|
|
4
|
-
// secp256k1
|
|
4
|
+
// secp256k1 prime (little-endian): 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFC2F
|
|
5
5
|
const P_BYTES: u8[] = [
|
|
6
|
-
0x2f, 0xfc, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
|
7
|
-
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
8
|
-
|
|
6
|
+
0x2f, 0xfc, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
|
7
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
8
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
9
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
10
|
+
];
|
|
9
11
|
|
|
12
|
+
// Gx (little-endian) = 79BE667E...F81798 reversed
|
|
10
13
|
const GX_BYTES: u8[] = [
|
|
11
|
-
0x98, 0x17, 0xf8, 0x16, 0xb1, 0x5b, 0x28, 0xd9,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
0x98, 0x17, 0xf8, 0x16, 0xb1, 0x5b, 0x28, 0xd9,
|
|
15
|
+
0x59, 0x28, 0xce, 0x2d, 0xdb, 0xfc, 0x9b, 0x02,
|
|
16
|
+
0x70, 0xb0, 0x87, 0xce, 0x95, 0xa0, 0x62, 0x55,
|
|
17
|
+
0xac, 0xbb, 0xdc, 0xf9, 0xef, 0x66, 0xbe, 0x79,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// Big-endian: 48 3A DA 77 26 A3 C4 65 5D A4 FB FC 0E 11 08 A8 FD 17 B4 48 A6 85 54 19 9C 47 D0 8F FB 10 D4 B8
|
|
21
|
+
// Little-endian reversal:
|
|
15
22
|
const GY_BYTES: u8[] = [
|
|
16
|
-
0xb8, 0xd4, 0x10, 0xfb,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
0xb8, 0xd4, 0x10, 0xfb, 0x8f, 0xd0, 0x47, 0x9c,
|
|
24
|
+
0x19, 0x54, 0x85, 0xa6, 0x48, 0xb4, 0x17, 0xfd,
|
|
25
|
+
0xa8, 0x08, 0x11, 0x0e, 0xfc, 0xfb, 0xa4, 0x5d,
|
|
26
|
+
0x65, 0xc4, 0xa3, 0x26, 0x77, 0xda, 0x3a, 0x48,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const P = u256.fromBytesLE(P_BYTES);
|
|
30
|
+
export const GX = u256.fromBytesLE(GX_BYTES);
|
|
31
|
+
export const GY = u256.fromBytesLE(GY_BYTES);
|
|
32
|
+
|
|
33
|
+
// Representing a point (x, y) on secp256k1
|
|
34
|
+
export class ECPoint {
|
|
27
35
|
x: u256;
|
|
28
36
|
y: u256;
|
|
29
37
|
|
|
@@ -32,90 +40,110 @@ class ECPoint {
|
|
|
32
40
|
this.y = y;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
//
|
|
43
|
+
// ----------------------------
|
|
44
|
+
// Point Doubling: 2P = P + P
|
|
45
|
+
// (for y^2 = x^3 + 7 with a=0)
|
|
46
|
+
// λ = (3*x^2) / (2*y) mod P
|
|
47
|
+
// x3 = λ^2 - 2x mod P
|
|
48
|
+
// y3 = λ*(x - x3) - y mod P
|
|
49
|
+
// ----------------------------
|
|
36
50
|
static double(p: ECPoint): ECPoint {
|
|
51
|
+
// If y=0, return infinity
|
|
37
52
|
if (p.y == u256.Zero) {
|
|
38
|
-
return new ECPoint(u256.Zero, u256.Zero); // Point at infinity
|
|
53
|
+
return new ECPoint(u256.Zero, u256.Zero); // "Point at infinity" convention
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
const two = u256.fromU64(2);
|
|
42
57
|
const three = u256.fromU64(3);
|
|
43
58
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const lambda = SafeMath.mod(SafeMath.mul(numerator, denominator), P);
|
|
59
|
+
// numerator = 3*x^2 mod P
|
|
60
|
+
const xSquared = SafeMath.pow(p.x, two);
|
|
61
|
+
const numerator = SafeMath.mod(SafeMath.mul(three, xSquared), P);
|
|
48
62
|
|
|
49
|
-
//
|
|
50
|
-
const
|
|
63
|
+
// denominator = (2*y)^-1 mod P
|
|
64
|
+
const twoY = SafeMath.mul(two, p.y);
|
|
65
|
+
const denominatorInv = SafeMath.modInverse(twoY, P);
|
|
51
66
|
|
|
52
|
-
//
|
|
53
|
-
const
|
|
67
|
+
// λ = numerator * denominator^-1 mod P
|
|
68
|
+
const lambda = SafeMath.mod(SafeMath.mul(numerator, denominatorInv), P);
|
|
69
|
+
|
|
70
|
+
// xr = λ^2 - 2x mod P
|
|
71
|
+
const lambdaSquared = SafeMath.pow(lambda, two);
|
|
72
|
+
const twoX = SafeMath.mul(two, p.x);
|
|
73
|
+
const xr = SafeMath.mod(SafeMath.sub(lambdaSquared, twoX), P);
|
|
74
|
+
|
|
75
|
+
// yr = λ*(x - xr) - y mod P
|
|
76
|
+
const xMinusXr = SafeMath.sub(p.x, xr);
|
|
77
|
+
const lambdaTimesXDiff = SafeMath.mul(lambda, xMinusXr);
|
|
78
|
+
const yr = SafeMath.mod(SafeMath.sub(lambdaTimesXDiff, p.y), P);
|
|
54
79
|
|
|
55
80
|
return new ECPoint(xr, yr);
|
|
56
81
|
}
|
|
57
82
|
|
|
58
|
-
//
|
|
83
|
+
// ----------------------------
|
|
84
|
+
// Point Addition: R = P + Q
|
|
85
|
+
// λ = (y2 - y1) / (x2 - x1) mod P
|
|
86
|
+
// x3 = λ^2 - x1 - x2 mod P
|
|
87
|
+
// y3 = λ*(x1 - x3) - y1 mod P
|
|
88
|
+
// ----------------------------
|
|
59
89
|
static add(p: ECPoint, q: ECPoint): ECPoint {
|
|
90
|
+
// 1) Check for infinity cases
|
|
91
|
+
const isPInfinity = p.x.isZero() && p.y.isZero();
|
|
92
|
+
const isQInfinity = q.x.isZero() && q.y.isZero();
|
|
93
|
+
|
|
94
|
+
if (isPInfinity) return q; // ∞ + Q = Q
|
|
95
|
+
if (isQInfinity) return p; // P + ∞ = P
|
|
96
|
+
|
|
97
|
+
// 2) Check if P == Q => doubling
|
|
60
98
|
if (p.x == q.x && p.y == q.y) {
|
|
61
|
-
return
|
|
99
|
+
return ECPoint.double(p);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3) Check if P == -Q => return infinity
|
|
103
|
+
// (x1 == x2, but y1 != y2 => P + Q = ∞)
|
|
104
|
+
if (p.x == q.x && p.y != q.y) {
|
|
105
|
+
return new ECPoint(u256.Zero, u256.Zero);
|
|
62
106
|
}
|
|
63
107
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
72
|
-
|
|
108
|
+
const numerator = SafeMath.sub(q.y, p.y);
|
|
109
|
+
const denominator = SafeMath.sub(q.x, p.x);
|
|
110
|
+
const denominatorInv = SafeMath.modInverse(denominator, P);
|
|
111
|
+
const lambda = SafeMath.mod(SafeMath.mul(numerator, denominatorInv), P);
|
|
112
|
+
|
|
113
|
+
// x3 = λ^2 - (x1 + x2) mod P
|
|
114
|
+
const lambdaSq = SafeMath.pow(lambda, u256.fromU64(2));
|
|
115
|
+
let xr = SafeMath.sub(lambdaSq, SafeMath.add(p.x, q.x));
|
|
116
|
+
xr = SafeMath.mod(xr, P);
|
|
117
|
+
|
|
118
|
+
// y3 = λ*(x1 - x3) - y1 mod P
|
|
119
|
+
const xDiff = SafeMath.sub(p.x, xr);
|
|
120
|
+
let yr = SafeMath.mul(lambda, xDiff);
|
|
121
|
+
yr = SafeMath.sub(yr, p.y);
|
|
122
|
+
yr = SafeMath.mod(yr, P);
|
|
73
123
|
|
|
74
124
|
return new ECPoint(xr, yr);
|
|
75
125
|
}
|
|
76
126
|
|
|
77
|
-
//
|
|
127
|
+
// ----------------------------
|
|
128
|
+
// Scalar Multiplication: k*P
|
|
129
|
+
// Double-and-add approach
|
|
130
|
+
// ----------------------------
|
|
78
131
|
static scalarMultiply(p: ECPoint, k: u256): ECPoint {
|
|
79
|
-
let result = new ECPoint(u256.Zero, u256.Zero); //
|
|
132
|
+
let result = new ECPoint(u256.Zero, u256.Zero); // ∞
|
|
80
133
|
let addend = p;
|
|
134
|
+
const two = u256.fromU64(2);
|
|
81
135
|
|
|
136
|
+
// While k != 0
|
|
82
137
|
while (!k.isZero()) {
|
|
138
|
+
// If k is odd => add
|
|
83
139
|
if (!SafeMath.isEven(k)) {
|
|
84
|
-
result =
|
|
140
|
+
result = ECPoint.add(result, addend);
|
|
85
141
|
}
|
|
86
|
-
|
|
87
|
-
|
|
142
|
+
// Double the point
|
|
143
|
+
addend = ECPoint.double(addend);
|
|
144
|
+
// "Divide" k by 2 => shift right by 1
|
|
145
|
+
k = SafeMath.div(k, two);
|
|
88
146
|
}
|
|
89
|
-
|
|
90
147
|
return result;
|
|
91
148
|
}
|
|
92
149
|
}
|
|
93
|
-
|
|
94
|
-
// Generate a valid elliptic curve point (public key) from a hash
|
|
95
|
-
export function generatePublicKeyFromHash(scalar: u256): u8[] {
|
|
96
|
-
// Convert hash to u256 scalar
|
|
97
|
-
//const scalar = u256.fromBytes(hash);
|
|
98
|
-
|
|
99
|
-
// Define the generator point on secp256k1 curve
|
|
100
|
-
const G = new ECPoint(GX, GY);
|
|
101
|
-
|
|
102
|
-
// Perform scalar multiplication to get public key point
|
|
103
|
-
const publicKeyPoint = ECPoint.scalarMultiply(G, scalar);
|
|
104
|
-
|
|
105
|
-
// Convert the point to bytes (compressed format)
|
|
106
|
-
return pointToBytes(publicKeyPoint);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Convert elliptic curve point to compressed byte array
|
|
110
|
-
function pointToBytes(point: ECPoint): u8[] {
|
|
111
|
-
const prefix: u8 = SafeMath.isEven(point.y) ? 0x02 : 0x03; // Compressed format prefix
|
|
112
|
-
const xBytes = point.x.toUint8Array(); // Convert X coordinate to bytes
|
|
113
|
-
|
|
114
|
-
const result = new Array<u8>(33); // 1 byte prefix + 32 bytes X coordinate
|
|
115
|
-
result[0] = prefix;
|
|
116
|
-
for (let i = 0; i < 32; i++) {
|
|
117
|
-
result[i + 1] = xBytes[i];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return result;
|
|
121
|
-
}
|