@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.4.5",
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.3",
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 (this.currentOffset > this.buffer.byteLength) {
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,address,uint256,bytes)'):
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(owner: Address, spender: Address, value: u256, signature: Uint8Array): boolean {
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
- // Regenerate the hash
248
- const writer = new BytesWriter(ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH);
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
- const hasAddress = this.balanceOfMap.has(owner);
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 TODO: Fix the typing because this is valid assembly-script syntax
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 TODO: Fix the typing because this is valid assembly-script syntax
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
@@ -45,7 +45,6 @@ export * from './math/abi';
45
45
  export * from './math/bytes';
46
46
  export * from './math/cyrb53';
47
47
  export * from './math/sha256';
48
- export * from './math/rnd';
49
48
  export * from './secp256k1/ECPoint';
50
49
 
51
50
  /** Memory */
@@ -1,11 +1,20 @@
1
1
  import { u128, u256 } from '@btc-vision/as-bignum/assembly';
2
2
 
3
- export function bytes(number: u256[]): Uint8Array {
4
- const result = new Uint8Array(32 * number.length);
5
- for (let i: u8 = 0; i < 32; i++) {
6
- const num: Uint8Array = number[31 - i].toUint8Array();
7
- for (let j: u8 = 0; j < u8(number.length); j++) {
8
- result[i + j * 32] = num[i];
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
- // Make sure that the buffer is 16 bytes long.
34
- if (buffer.length !== 16) {
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 curve parameters (using little-endian byte arrays)
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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
7
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
8
- ]; // P in little-endian
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, 0x59, 0x28, 0xce, 0x2d, 0xdb, 0xfc, 0x9b, 0x02,
12
- 0x70, 0xb0, 0x87, 0xce, 0x95, 0xa0, 0x62, 0x55, 0xac, 0xbb, 0xdc, 0xf9, 0xef, 0x66, 0xbe, 0x79,
13
- ]; // Gx in little-endian
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, 0xff, 0x08, 0x7d, 0xc4, 0x19, 0x54, 0x85, 0xa6, 0x48, 0x44, 0x17, 0xfd,
17
- 0xa8, 0x08, 0xe1, 0x0e, 0xfc, 0x4b, 0xa4, 0x5d, 0x65, 0xc4, 0xa3, 0xa6, 0x77, 0xda, 0x3a, 0x48,
18
- ]; // Gy in little-endian
19
-
20
- // Converting byte arrays to u256 using fromBytesLe (little-endian)
21
- const P = u256.fromBytesLE(P_BYTES);
22
- const GX = u256.fromBytesLE(GX_BYTES);
23
- const GY = u256.fromBytesLE(GY_BYTES);
24
-
25
- // Define a point on the elliptic curve
26
- class ECPoint {
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
- // Point doubling: P + P = 2P
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
- // lambda = (3 * p.x^2) / (2 * p.y) mod P
45
- const numerator = SafeMath.mod(SafeMath.mul(three, SafeMath.pow(p.x, two)), P);
46
- const denominator = SafeMath.modInverse(SafeMath.mul(two, p.y), P);
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
- // xr = lambda^2 - 2 * p.x mod P
50
- const xr = SafeMath.mod(SafeMath.sub(SafeMath.pow(lambda, two), SafeMath.mul(two, p.x)), P);
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
- // yr = lambda * (p.x - xr) - p.y mod P
53
- const yr = SafeMath.mod(SafeMath.sub(SafeMath.mul(lambda, SafeMath.sub(p.x, xr)), p.y), P);
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
- // Point addition: P + Q = R
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 this.double(p); // If P == Q, perform doubling
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 lambda = SafeMath.mod(
65
- SafeMath.mul(SafeMath.sub(q.y, p.y), SafeMath.modInverse(SafeMath.sub(q.x, p.x), P)),
66
- P,
67
- );
68
- const xr = SafeMath.mod(
69
- SafeMath.sub(SafeMath.pow(lambda, u256.fromU64(2)), SafeMath.add(p.x, q.x)),
70
- P,
71
- );
72
- const yr = SafeMath.mod(SafeMath.sub(SafeMath.mul(lambda, SafeMath.sub(p.x, xr)), p.y), P);
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
- // Scalar multiplication: k * P
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); // Point at infinity
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 = this.add(result, addend);
140
+ result = ECPoint.add(result, addend);
85
141
  }
86
- addend = this.double(addend);
87
- k = SafeMath.div(k, u256.fromU64(2)); // Right shift by 1
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
- }