@sip-protocol/sdk 0.6.5 → 0.6.8

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.
@@ -156,6 +156,9 @@ export class BrowserNoirProvider implements ProofProvider {
156
156
  private fulfillmentNoir: Noir | null = null
157
157
  private fulfillmentBackend: UltraHonkBackend | null = null
158
158
 
159
+ // Mutex for WASM operations (prevents BorrowMutError from concurrent access)
160
+ private wasmMutex: Promise<void> = Promise.resolve()
161
+
159
162
  // Worker instance (optional)
160
163
  private worker: Worker | null = null
161
164
  private workerPending: Map<
@@ -556,74 +559,79 @@ export class BrowserNoirProvider implements ProofProvider {
556
559
  throw new ProofGenerationError('funding', 'Funding circuit not initialized')
557
560
  }
558
561
 
559
- try {
560
- onProgress?.({
561
- stage: 'witness',
562
- percent: 10,
563
- message: 'Preparing witness inputs...',
564
- })
565
-
566
- // Convert blinding factor to field element
567
- const blindingField = this.bytesToField(params.blindingFactor)
568
-
569
- // New circuit signature: (minimum_required: pub u64, asset_id: pub Field, balance: u64, blinding: Field) -> [u8; 32]
570
- const witnessInputs = {
571
- minimum_required: params.minimumRequired.toString(),
572
- asset_id: `0x${this.assetIdToField(params.assetId)}`,
573
- balance: params.balance.toString(),
574
- blinding: blindingField,
575
- }
576
-
577
- onProgress?.({
578
- stage: 'witness',
579
- percent: 30,
580
- message: 'Generating witness...',
581
- })
582
-
583
- // Generate witness - circuit returns commitment hash as [u8; 32]
584
- const { witness, returnValue } = await this.fundingNoir.execute(witnessInputs)
585
-
586
- onProgress?.({
587
- stage: 'proving',
588
- percent: 50,
589
- message: 'Generating proof (this may take a moment)...',
590
- })
591
-
592
- // Generate proof
593
- const proofData = await this.fundingBackend.generateProof(witness)
562
+ // Use mutex to prevent concurrent WASM access (causes BorrowMutError)
563
+ return this.acquireWasmLock(async () => {
564
+ try {
565
+ onProgress?.({
566
+ stage: 'witness',
567
+ percent: 10,
568
+ message: 'Preparing witness inputs...',
569
+ })
570
+
571
+ // Convert blinding factor to field element
572
+ const blindingField = this.bytesToField(params.blindingFactor)
573
+
574
+ // Circuit signature: (minimum_required: pub Field, asset_id: pub Field, balance: Field, blinding: Field) -> [u8; 32]
575
+ // Using Field type for unlimited precision (handles NEAR's 24 decimals, etc.)
576
+ const witnessInputs = {
577
+ minimum_required: `0x${params.minimumRequired.toString(16)}`,
578
+ asset_id: `0x${this.assetIdToField(params.assetId)}`,
579
+ balance: `0x${params.balance.toString(16)}`,
580
+ blinding: blindingField,
581
+ }
594
582
 
595
- onProgress?.({
596
- stage: 'complete',
597
- percent: 100,
598
- message: 'Proof generated successfully',
599
- })
583
+ onProgress?.({
584
+ stage: 'witness',
585
+ percent: 30,
586
+ message: 'Generating witness...',
587
+ })
588
+
589
+ // Generate witness - circuit returns commitment hash as [u8; 32]
590
+ const { witness, returnValue } = await this.fundingNoir!.execute(witnessInputs)
591
+
592
+ onProgress?.({
593
+ stage: 'proving',
594
+ percent: 50,
595
+ message: 'Generating proof (this may take a moment)...',
596
+ })
597
+
598
+ // Generate proof
599
+ const proofData = await this.fundingBackend!.generateProof(witness)
600
+
601
+ onProgress?.({
602
+ stage: 'complete',
603
+ percent: 100,
604
+ message: 'Proof generated successfully',
605
+ })
606
+
607
+ // Extract commitment hash from circuit return value
608
+ const commitmentHashBytes = returnValue as number[]
609
+ const commitmentHashHex = bytesToHex(new Uint8Array(commitmentHashBytes))
610
+
611
+ // Order: minimum_required, asset_id, commitment_hash (return value)
612
+ // Field values padded to 64 hex chars (32 bytes)
613
+ const publicInputs: `0x${string}`[] = [
614
+ `0x${params.minimumRequired.toString(16).padStart(64, '0')}`,
615
+ `0x${this.assetIdToField(params.assetId)}`,
616
+ `0x${commitmentHashHex}`,
617
+ ]
618
+
619
+ const proof: ZKProof = {
620
+ type: 'funding',
621
+ proof: `0x${bytesToHex(proofData.proof)}`,
622
+ publicInputs,
623
+ }
600
624
 
601
- // Extract commitment hash from circuit return value
602
- const commitmentHashBytes = returnValue as number[]
603
- const commitmentHashHex = bytesToHex(new Uint8Array(commitmentHashBytes))
604
-
605
- // Order: minimum_required, asset_id, commitment_hash (return value)
606
- const publicInputs: `0x${string}`[] = [
607
- `0x${params.minimumRequired.toString(16).padStart(16, '0')}`,
608
- `0x${this.assetIdToField(params.assetId)}`,
609
- `0x${commitmentHashHex}`,
610
- ]
611
-
612
- const proof: ZKProof = {
613
- type: 'funding',
614
- proof: `0x${bytesToHex(proofData.proof)}`,
615
- publicInputs,
625
+ return { proof, publicInputs }
626
+ } catch (error) {
627
+ const message = error instanceof Error ? error.message : String(error)
628
+ throw new ProofGenerationError(
629
+ 'funding',
630
+ `Failed to generate funding proof: ${message}`,
631
+ error instanceof Error ? error : undefined
632
+ )
616
633
  }
617
-
618
- return { proof, publicInputs }
619
- } catch (error) {
620
- const message = error instanceof Error ? error.message : String(error)
621
- throw new ProofGenerationError(
622
- 'funding',
623
- `Failed to generate funding proof: ${message}`,
624
- error instanceof Error ? error : undefined
625
- )
626
- }
634
+ })
627
635
  }
628
636
 
629
637
  /**
@@ -641,105 +649,108 @@ export class BrowserNoirProvider implements ProofProvider {
641
649
  throw new ProofGenerationError('validity', 'Validity circuit not initialized')
642
650
  }
643
651
 
644
- try {
645
- onProgress?.({
646
- stage: 'witness',
647
- percent: 10,
648
- message: 'Preparing validity witness...',
649
- })
650
-
651
- // Convert inputs to field elements
652
- const intentHashField = this.hexToField(params.intentHash)
653
- const senderAddressField = this.hexToField(params.senderAddress)
654
- const senderBlindingField = this.bytesToField(params.senderBlinding)
655
- const senderSecretField = this.bytesToField(params.senderSecret)
656
- const nonceField = this.bytesToField(params.nonce)
657
-
658
- // Compute derived values
659
- const { commitmentX, commitmentY } = await this.computeSenderCommitment(
660
- senderAddressField,
661
- senderBlindingField
662
- )
663
- const nullifier = await this.computeNullifier(senderSecretField, intentHashField, nonceField)
664
-
665
- const signature = Array.from(params.authorizationSignature)
666
- const messageHash = this.fieldToBytes32(intentHashField)
667
-
668
- // Get public key
669
- let pubKeyX: number[]
670
- let pubKeyY: number[]
671
- if (params.senderPublicKey) {
672
- pubKeyX = Array.from(params.senderPublicKey.x)
673
- pubKeyY = Array.from(params.senderPublicKey.y)
674
- } else {
675
- const coords = this.getPublicKeyCoordinates(params.senderSecret)
676
- pubKeyX = coords.x
677
- pubKeyY = coords.y
678
- }
679
-
680
- const witnessInputs = {
681
- intent_hash: intentHashField,
682
- sender_commitment_x: commitmentX,
683
- sender_commitment_y: commitmentY,
684
- nullifier: nullifier,
685
- timestamp: params.timestamp.toString(),
686
- expiry: params.expiry.toString(),
687
- sender_address: senderAddressField,
688
- sender_blinding: senderBlindingField,
689
- sender_secret: senderSecretField,
690
- pub_key_x: pubKeyX,
691
- pub_key_y: pubKeyY,
692
- signature: signature,
693
- message_hash: messageHash,
694
- nonce: nonceField,
695
- }
696
-
697
- onProgress?.({
698
- stage: 'witness',
699
- percent: 30,
700
- message: 'Generating witness...',
701
- })
702
-
703
- const { witness } = await this.validityNoir.execute(witnessInputs)
704
-
705
- onProgress?.({
706
- stage: 'proving',
707
- percent: 50,
708
- message: 'Generating validity proof...',
709
- })
652
+ // Use mutex to prevent concurrent WASM access (causes BorrowMutError)
653
+ return this.acquireWasmLock(async () => {
654
+ try {
655
+ onProgress?.({
656
+ stage: 'witness',
657
+ percent: 10,
658
+ message: 'Preparing validity witness...',
659
+ })
660
+
661
+ // Convert inputs to field elements
662
+ const intentHashField = this.hexToField(params.intentHash)
663
+ const senderAddressField = this.hexToField(params.senderAddress)
664
+ const senderBlindingField = this.bytesToField(params.senderBlinding)
665
+ const senderSecretField = this.bytesToField(params.senderSecret)
666
+ const nonceField = this.bytesToField(params.nonce)
667
+
668
+ // Compute derived values
669
+ const { commitmentX, commitmentY } = await this.computeSenderCommitment(
670
+ senderAddressField,
671
+ senderBlindingField
672
+ )
673
+ const nullifier = await this.computeNullifier(senderSecretField, intentHashField, nonceField)
674
+
675
+ const signature = Array.from(params.authorizationSignature)
676
+ const messageHash = this.fieldToBytes32(intentHashField)
677
+
678
+ // Get public key
679
+ let pubKeyX: number[]
680
+ let pubKeyY: number[]
681
+ if (params.senderPublicKey) {
682
+ pubKeyX = Array.from(params.senderPublicKey.x)
683
+ pubKeyY = Array.from(params.senderPublicKey.y)
684
+ } else {
685
+ const coords = this.getPublicKeyCoordinates(params.senderSecret)
686
+ pubKeyX = coords.x
687
+ pubKeyY = coords.y
688
+ }
710
689
 
711
- const proofData = await this.validityBackend.generateProof(witness)
690
+ const witnessInputs = {
691
+ intent_hash: intentHashField,
692
+ sender_commitment_x: commitmentX,
693
+ sender_commitment_y: commitmentY,
694
+ nullifier: nullifier,
695
+ timestamp: params.timestamp.toString(),
696
+ expiry: params.expiry.toString(),
697
+ sender_address: senderAddressField,
698
+ sender_blinding: senderBlindingField,
699
+ sender_secret: senderSecretField,
700
+ pub_key_x: pubKeyX,
701
+ pub_key_y: pubKeyY,
702
+ signature: signature,
703
+ message_hash: messageHash,
704
+ nonce: nonceField,
705
+ }
712
706
 
713
- onProgress?.({
714
- stage: 'complete',
715
- percent: 100,
716
- message: 'Validity proof generated',
717
- })
707
+ onProgress?.({
708
+ stage: 'witness',
709
+ percent: 30,
710
+ message: 'Generating witness...',
711
+ })
712
+
713
+ const { witness } = await this.validityNoir!.execute(witnessInputs)
714
+
715
+ onProgress?.({
716
+ stage: 'proving',
717
+ percent: 50,
718
+ message: 'Generating validity proof...',
719
+ })
720
+
721
+ const proofData = await this.validityBackend!.generateProof(witness)
722
+
723
+ onProgress?.({
724
+ stage: 'complete',
725
+ percent: 100,
726
+ message: 'Validity proof generated',
727
+ })
728
+
729
+ const publicInputs: `0x${string}`[] = [
730
+ `0x${intentHashField}`,
731
+ `0x${commitmentX}`,
732
+ `0x${commitmentY}`,
733
+ `0x${nullifier}`,
734
+ `0x${params.timestamp.toString(16).padStart(16, '0')}`,
735
+ `0x${params.expiry.toString(16).padStart(16, '0')}`,
736
+ ]
737
+
738
+ const proof: ZKProof = {
739
+ type: 'validity',
740
+ proof: `0x${bytesToHex(proofData.proof)}`,
741
+ publicInputs,
742
+ }
718
743
 
719
- const publicInputs: `0x${string}`[] = [
720
- `0x${intentHashField}`,
721
- `0x${commitmentX}`,
722
- `0x${commitmentY}`,
723
- `0x${nullifier}`,
724
- `0x${params.timestamp.toString(16).padStart(16, '0')}`,
725
- `0x${params.expiry.toString(16).padStart(16, '0')}`,
726
- ]
727
-
728
- const proof: ZKProof = {
729
- type: 'validity',
730
- proof: `0x${bytesToHex(proofData.proof)}`,
731
- publicInputs,
744
+ return { proof, publicInputs }
745
+ } catch (error) {
746
+ const message = error instanceof Error ? error.message : String(error)
747
+ throw new ProofGenerationError(
748
+ 'validity',
749
+ `Failed to generate validity proof: ${message}`,
750
+ error instanceof Error ? error : undefined
751
+ )
732
752
  }
733
-
734
- return { proof, publicInputs }
735
- } catch (error) {
736
- const message = error instanceof Error ? error.message : String(error)
737
- throw new ProofGenerationError(
738
- 'validity',
739
- `Failed to generate validity proof: ${message}`,
740
- error instanceof Error ? error : undefined
741
- )
742
- }
753
+ })
743
754
  }
744
755
 
745
756
  /**
@@ -757,109 +768,112 @@ export class BrowserNoirProvider implements ProofProvider {
757
768
  throw new ProofGenerationError('fulfillment', 'Fulfillment circuit not initialized')
758
769
  }
759
770
 
760
- try {
761
- onProgress?.({
762
- stage: 'witness',
763
- percent: 10,
764
- message: 'Preparing fulfillment witness...',
765
- })
766
-
767
- const intentHashField = this.hexToField(params.intentHash)
768
- const recipientStealthField = this.hexToField(params.recipientStealth)
769
-
770
- const { commitmentX, commitmentY } = await this.computeOutputCommitment(
771
- params.outputAmount,
772
- params.outputBlinding
773
- )
774
-
775
- const solverSecretField = this.bytesToField(params.solverSecret)
776
- const solverId = await this.computeSolverId(solverSecretField)
777
- const outputBlindingField = this.bytesToField(params.outputBlinding)
778
-
779
- const attestation = params.oracleAttestation
780
- const attestationRecipientField = this.hexToField(attestation.recipient)
781
- const attestationTxHashField = this.hexToField(attestation.txHash)
782
- const oracleSignature = Array.from(attestation.signature)
783
- const oracleMessageHash = await this.computeOracleMessageHash(
784
- attestation.recipient,
785
- attestation.amount,
786
- attestation.txHash,
787
- attestation.blockNumber
788
- )
789
-
790
- const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0)
791
- const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0)
792
-
793
- const witnessInputs = {
794
- intent_hash: intentHashField,
795
- output_commitment_x: commitmentX,
796
- output_commitment_y: commitmentY,
797
- recipient_stealth: recipientStealthField,
798
- min_output_amount: params.minOutputAmount.toString(),
799
- solver_id: solverId,
800
- fulfillment_time: params.fulfillmentTime.toString(),
801
- expiry: params.expiry.toString(),
802
- output_amount: params.outputAmount.toString(),
803
- output_blinding: outputBlindingField,
804
- solver_secret: solverSecretField,
805
- attestation_recipient: attestationRecipientField,
806
- attestation_amount: attestation.amount.toString(),
807
- attestation_tx_hash: attestationTxHashField,
808
- attestation_block: attestation.blockNumber.toString(),
809
- oracle_signature: oracleSignature,
810
- oracle_message_hash: oracleMessageHash,
811
- oracle_pub_key_x: oraclePubKeyX,
812
- oracle_pub_key_y: oraclePubKeyY,
813
- }
814
-
815
- onProgress?.({
816
- stage: 'witness',
817
- percent: 30,
818
- message: 'Generating witness...',
819
- })
820
-
821
- const { witness } = await this.fulfillmentNoir.execute(witnessInputs)
822
-
823
- onProgress?.({
824
- stage: 'proving',
825
- percent: 50,
826
- message: 'Generating fulfillment proof...',
827
- })
828
-
829
- const proofData = await this.fulfillmentBackend.generateProof(witness)
771
+ // Use mutex to prevent concurrent WASM access (causes BorrowMutError)
772
+ return this.acquireWasmLock(async () => {
773
+ try {
774
+ onProgress?.({
775
+ stage: 'witness',
776
+ percent: 10,
777
+ message: 'Preparing fulfillment witness...',
778
+ })
779
+
780
+ const intentHashField = this.hexToField(params.intentHash)
781
+ const recipientStealthField = this.hexToField(params.recipientStealth)
782
+
783
+ const { commitmentX, commitmentY } = await this.computeOutputCommitment(
784
+ params.outputAmount,
785
+ params.outputBlinding
786
+ )
787
+
788
+ const solverSecretField = this.bytesToField(params.solverSecret)
789
+ const solverId = await this.computeSolverId(solverSecretField)
790
+ const outputBlindingField = this.bytesToField(params.outputBlinding)
791
+
792
+ const attestation = params.oracleAttestation
793
+ const attestationRecipientField = this.hexToField(attestation.recipient)
794
+ const attestationTxHashField = this.hexToField(attestation.txHash)
795
+ const oracleSignature = Array.from(attestation.signature)
796
+ const oracleMessageHash = await this.computeOracleMessageHash(
797
+ attestation.recipient,
798
+ attestation.amount,
799
+ attestation.txHash,
800
+ attestation.blockNumber
801
+ )
802
+
803
+ const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0)
804
+ const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0)
805
+
806
+ const witnessInputs = {
807
+ intent_hash: intentHashField,
808
+ output_commitment_x: commitmentX,
809
+ output_commitment_y: commitmentY,
810
+ recipient_stealth: recipientStealthField,
811
+ min_output_amount: params.minOutputAmount.toString(),
812
+ solver_id: solverId,
813
+ fulfillment_time: params.fulfillmentTime.toString(),
814
+ expiry: params.expiry.toString(),
815
+ output_amount: params.outputAmount.toString(),
816
+ output_blinding: outputBlindingField,
817
+ solver_secret: solverSecretField,
818
+ attestation_recipient: attestationRecipientField,
819
+ attestation_amount: attestation.amount.toString(),
820
+ attestation_tx_hash: attestationTxHashField,
821
+ attestation_block: attestation.blockNumber.toString(),
822
+ oracle_signature: oracleSignature,
823
+ oracle_message_hash: oracleMessageHash,
824
+ oracle_pub_key_x: oraclePubKeyX,
825
+ oracle_pub_key_y: oraclePubKeyY,
826
+ }
830
827
 
831
- onProgress?.({
832
- stage: 'complete',
833
- percent: 100,
834
- message: 'Fulfillment proof generated',
835
- })
828
+ onProgress?.({
829
+ stage: 'witness',
830
+ percent: 30,
831
+ message: 'Generating witness...',
832
+ })
833
+
834
+ const { witness } = await this.fulfillmentNoir!.execute(witnessInputs)
835
+
836
+ onProgress?.({
837
+ stage: 'proving',
838
+ percent: 50,
839
+ message: 'Generating fulfillment proof...',
840
+ })
841
+
842
+ const proofData = await this.fulfillmentBackend!.generateProof(witness)
843
+
844
+ onProgress?.({
845
+ stage: 'complete',
846
+ percent: 100,
847
+ message: 'Fulfillment proof generated',
848
+ })
849
+
850
+ const publicInputs: `0x${string}`[] = [
851
+ `0x${intentHashField}`,
852
+ `0x${commitmentX}`,
853
+ `0x${commitmentY}`,
854
+ `0x${recipientStealthField}`,
855
+ `0x${params.minOutputAmount.toString(16).padStart(16, '0')}`,
856
+ `0x${solverId}`,
857
+ `0x${params.fulfillmentTime.toString(16).padStart(16, '0')}`,
858
+ `0x${params.expiry.toString(16).padStart(16, '0')}`,
859
+ ]
860
+
861
+ const proof: ZKProof = {
862
+ type: 'fulfillment',
863
+ proof: `0x${bytesToHex(proofData.proof)}`,
864
+ publicInputs,
865
+ }
836
866
 
837
- const publicInputs: `0x${string}`[] = [
838
- `0x${intentHashField}`,
839
- `0x${commitmentX}`,
840
- `0x${commitmentY}`,
841
- `0x${recipientStealthField}`,
842
- `0x${params.minOutputAmount.toString(16).padStart(16, '0')}`,
843
- `0x${solverId}`,
844
- `0x${params.fulfillmentTime.toString(16).padStart(16, '0')}`,
845
- `0x${params.expiry.toString(16).padStart(16, '0')}`,
846
- ]
847
-
848
- const proof: ZKProof = {
849
- type: 'fulfillment',
850
- proof: `0x${bytesToHex(proofData.proof)}`,
851
- publicInputs,
867
+ return { proof, publicInputs }
868
+ } catch (error) {
869
+ const message = error instanceof Error ? error.message : String(error)
870
+ throw new ProofGenerationError(
871
+ 'fulfillment',
872
+ `Failed to generate fulfillment proof: ${message}`,
873
+ error instanceof Error ? error : undefined
874
+ )
852
875
  }
853
-
854
- return { proof, publicInputs }
855
- } catch (error) {
856
- const message = error instanceof Error ? error.message : String(error)
857
- throw new ProofGenerationError(
858
- 'fulfillment',
859
- `Failed to generate fulfillment proof: ${message}`,
860
- error instanceof Error ? error : undefined
861
- )
862
- }
876
+ })
863
877
  }
864
878
 
865
879
  /**
@@ -891,24 +905,29 @@ export class BrowserNoirProvider implements ProofProvider {
891
905
  )
892
906
  }
893
907
 
894
- try {
895
- const proofHex = proof.proof.startsWith('0x') ? proof.proof.slice(2) : proof.proof
896
- const proofBytes = hexToBytes(proofHex)
897
-
898
- const isValid = await backend.verifyProof({
899
- proof: proofBytes,
900
- publicInputs: proof.publicInputs.map((input) =>
901
- input.startsWith('0x') ? input.slice(2) : input
902
- ),
903
- })
904
-
905
- return isValid
906
- } catch (error) {
907
- if (this.config.verbose) {
908
- console.error('[BrowserNoirProvider] Verification error:', error)
908
+ const backendToUse = backend
909
+
910
+ // Use mutex to prevent concurrent WASM access (causes BorrowMutError)
911
+ return this.acquireWasmLock(async () => {
912
+ try {
913
+ const proofHex = proof.proof.startsWith('0x') ? proof.proof.slice(2) : proof.proof
914
+ const proofBytes = hexToBytes(proofHex)
915
+
916
+ const isValid = await backendToUse.verifyProof({
917
+ proof: proofBytes,
918
+ publicInputs: proof.publicInputs.map((input) =>
919
+ input.startsWith('0x') ? input.slice(2) : input
920
+ ),
921
+ })
922
+
923
+ return isValid
924
+ } catch (error) {
925
+ if (this.config.verbose) {
926
+ console.error('[BrowserNoirProvider] Verification error:', error)
927
+ }
928
+ return false
909
929
  }
910
- return false
911
- }
930
+ })
912
931
  }
913
932
 
914
933
  /**
@@ -939,6 +958,34 @@ export class BrowserNoirProvider implements ProofProvider {
939
958
 
940
959
  // ─── Private Utility Methods ────────────────────────────────────────────────
941
960
 
961
+ /**
962
+ * Acquire WASM mutex lock for exclusive access
963
+ *
964
+ * The ACVM WASM module uses Rust RefCell internally which panics on
965
+ * concurrent mutable borrows. This mutex ensures serial execution.
966
+ */
967
+ private async acquireWasmLock<T>(operation: () => Promise<T>): Promise<T> {
968
+ // Wait for any pending operation to complete
969
+ const previousLock = this.wasmMutex
970
+
971
+ // Create a new lock that will be released when our operation completes
972
+ let releaseLock: () => void
973
+ this.wasmMutex = new Promise<void>((resolve) => {
974
+ releaseLock = resolve
975
+ })
976
+
977
+ try {
978
+ // Wait for previous operation
979
+ await previousLock
980
+
981
+ // Execute our operation
982
+ return await operation()
983
+ } finally {
984
+ // Release the lock
985
+ releaseLock!()
986
+ }
987
+ }
988
+
942
989
  private ensureReady(): void {
943
990
  if (!this._isReady) {
944
991
  throw new ProofError(