@btc-vision/btc-runtime 1.10.3 → 1.10.5

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.10.3",
3
+ "version": "1.10.5",
4
4
  "description": "Bitcoin Smart Contract Runtime",
5
5
  "main": "btc/index.ts",
6
6
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "@assemblyscript/loader": "^0.28.9",
47
- "@btc-vision/as-bignum": "^0.0.5",
47
+ "@btc-vision/as-bignum": "^0.0.6",
48
48
  "@btc-vision/opnet-transform": "^0.1.12",
49
49
  "@eslint/js": "9.39.1",
50
50
  "gulplog": "^2.2.0",
@@ -43,6 +43,7 @@ import {
43
43
  import { IOP20 } from './interfaces/IOP20';
44
44
  import { OP20InitParameters } from './interfaces/OP20InitParameters';
45
45
  import { ReentrancyGuard, ReentrancyLevel } from './ReentrancyGuard';
46
+ import { ExtendedAddress } from '../types/ExtendedAddress';
46
47
 
47
48
  const nonceMapPointer: u16 = Blockchain.nextPointer;
48
49
  const maxSupplyPointer: u16 = Blockchain.nextPointer;
@@ -486,7 +487,8 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
486
487
  * Uses Schnorr signatures now, will support ML-DSA after quantum transition.
487
488
  */
488
489
  @method(
489
- { name: 'owner', type: ABIDataTypes.ADDRESS },
490
+ { name: 'owner', type: ABIDataTypes.BYTES32 },
491
+ { name: 'ownerTweakedPublicKey', type: ABIDataTypes.BYTES32 },
490
492
  { name: 'spender', type: ABIDataTypes.ADDRESS },
491
493
  { name: 'amount', type: ABIDataTypes.UINT256 },
492
494
  { name: 'deadline', type: ABIDataTypes.UINT64 },
@@ -494,7 +496,11 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
494
496
  )
495
497
  @emit('Approved')
496
498
  public increaseAllowanceBySignature(calldata: Calldata): BytesWriter {
497
- const owner: Address = calldata.readAddress();
499
+ const ownerAddress = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
500
+ const ownerTweakedPublicKey = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
501
+
502
+ const owner = new ExtendedAddress(ownerTweakedPublicKey, ownerAddress);
503
+
498
504
  const spender: Address = calldata.readAddress();
499
505
  const amount: u256 = calldata.readU256();
500
506
  const deadline: u64 = calldata.readU64();
@@ -513,7 +519,8 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
513
519
  * @throws {Revert} If signature is invalid or expired
514
520
  */
515
521
  @method(
516
- { name: 'owner', type: ABIDataTypes.ADDRESS },
522
+ { name: 'owner', type: ABIDataTypes.BYTES32 },
523
+ { name: 'ownerTweakedPublicKey', type: ABIDataTypes.BYTES32 },
517
524
  { name: 'spender', type: ABIDataTypes.ADDRESS },
518
525
  { name: 'amount', type: ABIDataTypes.UINT256 },
519
526
  { name: 'deadline', type: ABIDataTypes.UINT64 },
@@ -521,7 +528,11 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
521
528
  )
522
529
  @emit('Approved')
523
530
  public decreaseAllowanceBySignature(calldata: Calldata): BytesWriter {
524
- const owner: Address = calldata.readAddress();
531
+ const ownerAddress = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
532
+ const ownerTweakedPublicKey = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
533
+
534
+ const owner = new ExtendedAddress(ownerTweakedPublicKey, ownerAddress);
535
+
525
536
  const spender: Address = calldata.readAddress();
526
537
  const amount: u256 = calldata.readU256();
527
538
  const deadline: u64 = calldata.readU64();
@@ -714,7 +725,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
714
725
  * @protected
715
726
  */
716
727
  protected _increaseAllowanceBySignature(
717
- owner: Address,
728
+ owner: ExtendedAddress,
718
729
  spender: Address,
719
730
  amount: u256,
720
731
  deadline: u64,
@@ -760,7 +771,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
760
771
  * @protected
761
772
  */
762
773
  protected _decreaseAllowanceBySignature(
763
- owner: Address,
774
+ owner: ExtendedAddress,
764
775
  spender: Address,
765
776
  amount: u256,
766
777
  deadline: u64,
@@ -783,7 +794,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
783
794
  */
784
795
  protected _verifySignature(
785
796
  typeHash: u8[],
786
- owner: Address,
797
+ owner: ExtendedAddress,
787
798
  spender: Address,
788
799
  amount: u256,
789
800
  deadline: u64,
@@ -817,7 +828,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
817
828
 
818
829
  const hash = sha256(messageWriter.getBuffer());
819
830
 
820
- if (!Blockchain.verifySchnorrSignature(owner, signature, hash)) {
831
+ if (!Blockchain.verifySignature(owner, signature, hash)) {
821
832
  throw new Revert('Invalid signature');
822
833
  }
823
834
 
@@ -39,6 +39,7 @@ import {
39
39
  OP721_APPROVAL_FOR_ALL_TYPE_HASH,
40
40
  OP721_APPROVAL_TYPE_HASH,
41
41
  } from '../constants/Exports';
42
+ import { ExtendedAddress } from '../types/ExtendedAddress';
42
43
 
43
44
  const stringPointer: u16 = Blockchain.nextPointer;
44
45
  const totalSupplyPointer: u16 = Blockchain.nextPointer;
@@ -395,7 +396,8 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
395
396
  }
396
397
 
397
398
  @method(
398
- { name: 'owner', type: ABIDataTypes.ADDRESS },
399
+ { name: 'owner', type: ABIDataTypes.BYTES32 },
400
+ { name: 'ownerTweakedPublicKey', type: ABIDataTypes.BYTES32 },
399
401
  { name: 'operator', type: ABIDataTypes.ADDRESS },
400
402
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
401
403
  { name: 'deadline', type: ABIDataTypes.UINT64 },
@@ -403,7 +405,11 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
403
405
  )
404
406
  @emit('Approved')
405
407
  public approveBySignature(calldata: Calldata): BytesWriter {
406
- const owner = calldata.readAddress();
408
+ const ownerAddress = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
409
+ const ownerTweakedPublicKey = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
410
+
411
+ const owner = new ExtendedAddress(ownerTweakedPublicKey, ownerAddress);
412
+
407
413
  const operator = calldata.readAddress();
408
414
  const tokenId = calldata.readU256();
409
415
  const deadline = calldata.readU64();
@@ -421,7 +427,8 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
421
427
  }
422
428
 
423
429
  @method(
424
- { name: 'owner', type: ABIDataTypes.ADDRESS },
430
+ { name: 'owner', type: ABIDataTypes.BYTES32 },
431
+ { name: 'ownerTweakedPublicKey', type: ABIDataTypes.BYTES32 },
425
432
  { name: 'operator', type: ABIDataTypes.ADDRESS },
426
433
  { name: 'approved', type: ABIDataTypes.BOOL },
427
434
  { name: 'deadline', type: ABIDataTypes.UINT64 },
@@ -429,7 +436,11 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
429
436
  )
430
437
  @emit('Approved')
431
438
  public setApprovalForAllBySignature(calldata: Calldata): BytesWriter {
432
- const owner = calldata.readAddress();
439
+ const ownerAddress = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
440
+ const ownerTweakedPublicKey = calldata.readBytesArray(ADDRESS_BYTE_LENGTH);
441
+
442
+ const owner = new ExtendedAddress(ownerTweakedPublicKey, ownerAddress);
443
+
433
444
  const operator = calldata.readAddress();
434
445
  const approved = calldata.readBoolean();
435
446
  const deadline = calldata.readU64();
@@ -784,7 +795,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
784
795
  }
785
796
 
786
797
  protected _verifyApproveSignature(
787
- owner: Address,
798
+ owner: ExtendedAddress,
788
799
  spender: Address,
789
800
  tokenId: u256,
790
801
  deadline: u64,
@@ -814,7 +825,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
814
825
  }
815
826
 
816
827
  protected _verifySetApprovalForAllSignature(
817
- owner: Address,
828
+ owner: ExtendedAddress,
818
829
  spender: Address,
819
830
  approved: boolean,
820
831
  deadline: u64,
@@ -845,7 +856,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
845
856
 
846
857
  protected _verifySignature(
847
858
  structHash: Uint8Array,
848
- owner: Address,
859
+ owner: ExtendedAddress,
849
860
  signature: Uint8Array,
850
861
  nonce: u256,
851
862
  ): void {
@@ -856,7 +867,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
856
867
 
857
868
  const hash = sha256(messageWriter.getBuffer());
858
869
 
859
- if (!Blockchain.verifySchnorrSignature(owner, signature, hash)) {
870
+ if (!Blockchain.verifySignature(owner, signature, hash)) {
860
871
  throw new Revert('Invalid signature');
861
872
  }
862
873
 
@@ -50,6 +50,12 @@ export class CallResult {
50
50
  ) {}
51
51
  }
52
52
 
53
+ const SCRATCH_SIZE: i32 = 256;
54
+ const SCRATCH_BUF: ArrayBuffer = new ArrayBuffer(SCRATCH_SIZE);
55
+ const SCRATCH_VIEW: Uint8Array = Uint8Array.wrap(SCRATCH_BUF);
56
+
57
+ const FOUR_BYTES_UINT8ARRAY_MEMORY_CACHE = new Uint8Array(4);
58
+
53
59
  /**
54
60
  * BlockchainEnvironment - Core Runtime Environment for OP_NET Smart Contracts
55
61
  *
@@ -282,9 +288,10 @@ export class BlockchainEnvironment {
282
288
  * Called once during deployment. State changes here are permanent.
283
289
  */
284
290
  public onDeployment(calldata: Calldata): void {
285
- for (let i: i32 = 0; i < this._plugins.length; i++) {
286
- const plugin = this._plugins[i];
287
- plugin.onDeployment(calldata);
291
+ const len = this._plugins.length;
292
+ for (let i: i32 = 0; i < len; i++) {
293
+ // Unchecked access for speed
294
+ unchecked(this._plugins[i].onDeployment(calldata));
288
295
  }
289
296
  this.contract.onDeployment(calldata);
290
297
  }
@@ -299,9 +306,9 @@ export class BlockchainEnvironment {
299
306
  * Used for access control, reentrancy guards, and validation.
300
307
  */
301
308
  public onExecutionStarted(selector: Selector, calldata: Calldata): void {
302
- for (let i: i32 = 0; i < this._plugins.length; i++) {
303
- const plugin = this._plugins[i];
304
- plugin.onExecutionStarted(selector, calldata);
309
+ const len = this._plugins.length;
310
+ for (let i: i32 = 0; i < len; i++) {
311
+ unchecked(this._plugins[i].onExecutionStarted(selector, calldata));
305
312
  }
306
313
  this.contract.onExecutionStarted(selector, calldata);
307
314
  }
@@ -316,9 +323,9 @@ export class BlockchainEnvironment {
316
323
  * Only called on successful execution. Used for cleanup and events.
317
324
  */
318
325
  public onExecutionCompleted(selector: Selector, calldata: Calldata): void {
319
- for (let i: i32 = 0; i < this._plugins.length; i++) {
320
- const plugin = this._plugins[i];
321
- plugin.onExecutionCompleted(selector, calldata);
326
+ const len = this._plugins.length;
327
+ for (let i: i32 = 0; i < len; i++) {
328
+ unchecked(this._plugins[i].onExecutionCompleted(selector, calldata));
322
329
  }
323
330
  this.contract.onExecutionCompleted(selector, calldata);
324
331
  }
@@ -332,6 +339,7 @@ export class BlockchainEnvironment {
332
339
  * Called automatically by the runtime to set up execution context.
333
340
  */
334
341
  public setEnvironmentVariables(data: Uint8Array): void {
342
+ // BytesReader is unavoidable for parsing complex external struct
335
343
  const reader: BytesReader = new BytesReader(data);
336
344
 
337
345
  const blockHash = reader.readBytes(32);
@@ -404,16 +412,18 @@ export class BlockchainEnvironment {
404
412
  throw new Revert('Destination contract is required');
405
413
  }
406
414
 
407
- const resultLengthBuffer = new ArrayBuffer(32);
415
+ // This creates the underlying ArrayBuffer AND gives us a 'dataStart' pointer for free.
408
416
  const status = callContract(
409
417
  destinationContract.buffer,
410
418
  calldata.getBuffer().buffer,
411
419
  calldata.bufferLength(),
412
- resultLengthBuffer,
420
+ FOUR_BYTES_UINT8ARRAY_MEMORY_CACHE.buffer, // Pass the underlying ArrayBuffer to the host
413
421
  );
414
422
 
415
- const reader = new BytesReader(Uint8Array.wrap(resultLengthBuffer));
416
- const resultLength = reader.readU32(true);
423
+ // OPTIMIZATION: Read raw memory directly using load<u32>
424
+ // We use .dataStart to get the raw pointer to the payload.
425
+ const resultLength = bswap<u32>(load<u32>(FOUR_BYTES_UINT8ARRAY_MEMORY_CACHE.dataStart));
426
+
417
427
  const resultBuffer = new ArrayBuffer(resultLength);
418
428
  getCallResult(0, resultLength, resultBuffer);
419
429
 
@@ -474,14 +484,29 @@ export class BlockchainEnvironment {
474
484
  */
475
485
  public emit(event: NetEvent): void {
476
486
  const data = event.getEventData();
477
- const writer = new BytesWriter(
478
- String.UTF8.byteLength(event.eventType) + 8 + data.byteLength,
479
- );
487
+ const eventType = event.eventType;
488
+ const typeLen = String.UTF8.byteLength(eventType);
489
+
490
+ // Structure: [4 bytes type len] + [type bytes] + [4 bytes data len] + [data bytes]
491
+ const totalLen = 8 + typeLen + data.length;
492
+
493
+ const writer = new Uint8Array(totalLen);
494
+ const ptr = writer.dataStart;
480
495
 
481
- writer.writeStringWithLength(event.eventType);
482
- writer.writeBytesWithLength(data);
496
+ // Write type length (BE)
497
+ store<u32>(ptr, bswap<u32>(typeLen));
483
498
 
484
- emit(writer.getBuffer().buffer, writer.bufferLength());
499
+ // Write type string
500
+ String.UTF8.encodeUnsafe(changetype<usize>(eventType), eventType.length, ptr + 4);
501
+
502
+ // Write data length (BE)
503
+ const offset = 4 + typeLen;
504
+ store<u32>(ptr + offset, bswap<u32>(data.length));
505
+
506
+ // Write data bytes (Safe memory copy)
507
+ memory.copy(ptr + offset + 4, data.dataStart, data.length);
508
+
509
+ emit(writer.buffer, totalLen);
485
510
  }
486
511
 
487
512
  /**
@@ -502,11 +527,21 @@ export class BlockchainEnvironment {
502
527
  * ```
503
528
  */
504
529
  public validateBitcoinAddress(address: string): bool {
505
- const writer = new BytesWriter(String.UTF8.byteLength(address));
506
- writer.writeString(address);
530
+ const len = String.UTF8.byteLength(address);
507
531
 
508
- const result = validateBitcoinAddress(writer.getBuffer().buffer, address.length);
509
- return result === 1;
532
+ if (len <= SCRATCH_SIZE) {
533
+ String.UTF8.encodeUnsafe(
534
+ changetype<usize>(address),
535
+ address.length,
536
+ SCRATCH_VIEW.dataStart,
537
+ );
538
+
539
+ return validateBitcoinAddress(SCRATCH_BUF, len) === 1;
540
+ } else {
541
+ const writer = new BytesWriter(len);
542
+ writer.writeString(address);
543
+ return validateBitcoinAddress(writer.getBuffer().buffer, len) === 1;
544
+ }
510
545
  }
511
546
 
512
547
  /**
@@ -672,7 +707,7 @@ export class BlockchainEnvironment {
672
707
  * ```
673
708
  */
674
709
  public verifySchnorrSignature(
675
- publicKey: Address,
710
+ publicKey: ExtendedAddress,
676
711
  signature: Uint8Array,
677
712
  hash: Uint8Array,
678
713
  ): boolean {
@@ -732,32 +767,29 @@ export class BlockchainEnvironment {
732
767
  ): boolean {
733
768
  const publicKeyLength = MLDSAMetadata.fromLevel(level);
734
769
  if (publicKey.length !== (publicKeyLength as i32)) {
735
- throw new Revert(
736
- `Invalid ML-DSA public key length. Expected ${publicKeyLength}, got ${publicKey.length}`,
737
- );
770
+ throw new Revert(`Invalid ML-DSA public key length.`);
738
771
  }
739
772
 
740
- const signatureLength = MLDSAMetadata.signatureLen(publicKeyLength);
741
- if (signature.length !== signatureLength) {
742
- throw new Revert(
743
- `Invalid ML-DSA signature length. Expected ${signatureLength}, got ${signature.length}`,
744
- );
773
+ if (signature.length !== MLDSAMetadata.signatureLen(publicKeyLength)) {
774
+ throw new Revert(`Invalid ML-DSA signature length.`);
745
775
  }
746
776
 
747
777
  if (hash.length !== 32) {
748
- throw new Revert(`Invalid hash length. Expected 32, got ${hash.length}`);
778
+ throw new Revert(`Invalid hash length.`);
749
779
  }
750
780
 
751
- const writer = new BytesWriter(2 + publicKey.length);
752
- writer.writeU8(<u8>SignaturesMethods.MLDSA);
753
- writer.writeU8(<u8>level);
754
- writer.writeBytes(publicKey);
781
+ const bufferLen = 2 + publicKey.length;
782
+ const writer = new Uint8Array(bufferLen);
783
+ const ptr = writer.dataStart;
755
784
 
756
- const result: u32 = verifySignature(
757
- writer.getBuffer().buffer,
758
- signature.buffer,
759
- hash.buffer,
760
- );
785
+ // Single bytes - Endianness irrelevant
786
+ store<u8>(ptr, <u8>SignaturesMethods.MLDSA);
787
+ store<u8>(ptr + 1, <u8>level);
788
+
789
+ // Byte array copy
790
+ memory.copy(ptr + 2, publicKey.dataStart, publicKey.length);
791
+
792
+ const result: u32 = verifySignature(writer.buffer, signature.buffer, hash.buffer);
761
793
 
762
794
  return result === 1;
763
795
  }
@@ -822,7 +854,7 @@ export class BlockchainEnvironment {
822
854
  * ```
823
855
  */
824
856
  public verifySignature(
825
- address: Address,
857
+ address: ExtendedAddress,
826
858
  signature: Uint8Array,
827
859
  hash: Uint8Array,
828
860
  forceMLDSA: boolean = false,
@@ -981,29 +1013,23 @@ export class BlockchainEnvironment {
981
1013
  }
982
1014
 
983
1015
  private internalVerifySchnorr(
984
- publicKey: Address,
1016
+ publicKey: ExtendedAddress,
985
1017
  signature: Uint8Array,
986
1018
  hash: Uint8Array,
987
1019
  ): boolean {
988
- if (signature.byteLength !== 64) {
989
- throw new Revert(`Invalid signature length. Expected 64, got ${signature.length}`);
990
- }
1020
+ if (signature.length !== 64) throw new Revert(`Invalid signature length.`);
1021
+ if (hash.length !== 32) throw new Revert(`Invalid hash length.`);
991
1022
 
992
- if (hash.byteLength !== 32) {
993
- throw new Revert(`Invalid hash length. Expected 32, got ${hash.length}`);
994
- }
1023
+ // 1 byte prefix + 32 bytes address
1024
+ const totalLen = 1 + ADDRESS_BYTE_LENGTH;
1025
+ const buffer = new Uint8Array(totalLen);
1026
+ const ptr = buffer.dataStart;
995
1027
 
996
- const writer = new BytesWriter(1 + ADDRESS_BYTE_LENGTH);
997
- writer.writeU8(<u8>SignaturesMethods.Schnorr);
998
- writer.writeAddress(publicKey);
1028
+ store<u8>(ptr, <u8>SignaturesMethods.Schnorr);
999
1029
 
1000
- const result: u32 = verifySignature(
1001
- writer.getBuffer().buffer,
1002
- signature.buffer,
1003
- hash.buffer,
1004
- );
1030
+ memory.copy(ptr + 1, publicKey.tweakedPublicKey.dataStart, ADDRESS_BYTE_LENGTH);
1005
1031
 
1006
- return result === 1;
1032
+ return verifySignature(buffer.buffer, signature.buffer, hash.buffer) === 1;
1007
1033
  }
1008
1034
 
1009
1035
  private createContractIfNotExists(): void {
@@ -1017,17 +1043,16 @@ export class BlockchainEnvironment {
1017
1043
  }
1018
1044
 
1019
1045
  private _internalSetStorageAt(pointerHash: Uint8Array, value: Uint8Array): void {
1020
- if (pointerHash.buffer.byteLength !== 32) {
1046
+ if (pointerHash.length !== 32) {
1021
1047
  throw new Revert('Pointer must be 32 bytes long');
1022
1048
  }
1023
1049
 
1024
1050
  let finalValue: Uint8Array = value;
1025
- if (value.buffer.byteLength !== 32) {
1026
- // Auto-pad values shorter than 32 bytes
1051
+ if (value.length !== 32) {
1052
+ // Optimization: Pad manually using memory.copy to avoid loop
1027
1053
  finalValue = new Uint8Array(32);
1028
- for (let i = 0; i < value.buffer.byteLength && i < 32; i++) {
1029
- finalValue[i] = value[i];
1030
- }
1054
+ const len = value.length < 32 ? value.length : 32;
1055
+ memory.copy(finalValue.dataStart, value.dataStart, len);
1031
1056
  }
1032
1057
 
1033
1058
  this.storage.set(pointerHash, finalValue);
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable */
2
2
 
3
3
  import { MLDSAMetadata, MLDSASecurityLevel } from './consensus/MLDSAMetadata';
4
- import { BytesReader } from '../buffer/BytesReader';
5
- import { BytesWriter } from '../buffer/BytesWriter';
6
4
  import { ADDRESS_BYTE_LENGTH } from '../utils';
7
5
 
8
6
  /**
@@ -331,24 +329,36 @@ declare function loadMLDSA(key: ArrayBuffer, result: ArrayBuffer): void;
331
329
  * ```
332
330
  */
333
331
  export function loadMLDSAPublicKey(address: Uint8Array, level: MLDSASecurityLevel): Uint8Array {
334
- const length = MLDSAMetadata.fromLevel(level);
335
- const resultBuffer = new Uint8Array(
336
- 1 + length,
337
- );
332
+ const length = MLDSAMetadata.fromLevel(level) as i32;
338
333
 
339
- const writer = new BytesWriter(1 + ADDRESS_BYTE_LENGTH);
340
- writer.writeU8(level as u8);
341
- writer.writeBytes(address);
334
+ // Prepare Input: [Level (1 byte) + Address (32 bytes)]
335
+ // Allocation is cheap for small fixed sizes.
336
+ const inputBuffer = new Uint8Array(1 + ADDRESS_BYTE_LENGTH);
337
+ const inputPtr = inputBuffer.dataStart;
342
338
 
343
- loadMLDSA(writer.getBuffer().buffer, resultBuffer.buffer);
339
+ store<u8>(inputPtr, level as u8);
344
340
 
345
- const result = new BytesReader(resultBuffer);
346
- const exist = result.readBoolean();
347
- if (!exist) {
341
+ // Copy address bytes directly
342
+ memory.copy(inputPtr + 1, address.dataStart, ADDRESS_BYTE_LENGTH);
343
+
344
+ // Prepare Output: [Exists (1 byte) + Key (length bytes)]
345
+ const resultBuffer = new Uint8Array(1 + length);
346
+
347
+ // Host Call
348
+ loadMLDSA(inputBuffer.buffer, resultBuffer.buffer);
349
+
350
+ // Check Exists (Byte 0) via direct load
351
+ if (load<u8>(resultBuffer.dataStart) === 0) {
348
352
  throw new Error('ML-DSA public key not found');
349
353
  }
350
354
 
351
- return result.readBytes(length);
355
+ // Return Key
356
+ // slice(1) creates a new buffer with just the key.
357
+
358
+ // Note: using .subarray(1) would be O(1) (view) vs .slice(1) which is O(N) (copy).
359
+ // slice is safer if you need a standalone buffer, but subarray is faster for gas.
360
+ // Sticking to slice to match original behavior (fresh buffer).
361
+ return resultBuffer.slice(1);
352
362
  }
353
363
 
354
364
  export * from './Atomic';