@otskit/client 0.6.0 → 0.7.0

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/dist/browser.d.ts CHANGED
@@ -31,6 +31,12 @@ declare class OpenTimestampsBrowserClient {
31
31
  constructor(options?: BrowserClientOptions);
32
32
  /** hash: 32-byte Uint8Array or 64-char hex. Returns the pending .ots as Uint8Array. */
33
33
  stamp(hash: Uint8Array | string): Promise<Uint8Array>;
34
+ /**
35
+ * proof: a pending .ots as bytes. Queries the calendars it references (allowlisted) and returns
36
+ * the upgraded .ots. Throws UpgradeError if no calendar has confirmed the timestamp yet, which is
37
+ * the normal state until Bitcoin mines the commitment (typically an hour or more after stamping).
38
+ */
39
+ upgrade(proof: Uint8Array): Promise<Uint8Array>;
34
40
  }
35
41
 
36
42
  export { BROWSER_CALENDARS, type BrowserClientOptions, OpenTimestampsBrowserClient, bytesToHex, hashBlob, hashBytes };
package/dist/browser.js CHANGED
@@ -20,6 +20,8 @@ var StampError = class extends OpenTimestampsClientError {
20
20
  this.failedSubmissions = failed;
21
21
  }
22
22
  };
23
+ var UpgradeError = class extends OpenTimestampsClientError {
24
+ };
23
25
  var NetworkError = class extends OpenTimestampsClientError {
24
26
  /** HTTP status code when the failure originates from an HTTP response. */
25
27
  status;
@@ -653,6 +655,63 @@ async function orchestrateStamp(hash, calendars, networkLayer, validateCalendarU
653
655
  return detached.serializeToBytes();
654
656
  }
655
657
 
658
+ // src/core/upgrade-core.ts
659
+ import { DetachedTimestampFile as DetachedTimestampFile2 } from "@otskit/core";
660
+ function bytesEqual(a, b) {
661
+ if (a.length !== b.length) return false;
662
+ let diff = 0;
663
+ for (let i = 0; i < a.length; i++) {
664
+ diff |= a[i] ^ b[i];
665
+ }
666
+ return diff === 0;
667
+ }
668
+ async function upgradeProofBytes(incompleteProof, networkLayer, logger, signal) {
669
+ let detached;
670
+ try {
671
+ detached = DetachedTimestampFile2.deserialize(incompleteProof);
672
+ } catch (error) {
673
+ throw new ValidationError("Invalid .ots proof format", {
674
+ /* v8 ignore next */
675
+ ...error instanceof Error ? { cause: error } : {}
676
+ });
677
+ }
678
+ if (detached.timestamp.isTimestampComplete()) {
679
+ logger?.info("Proof already complete; nothing to upgrade");
680
+ return incompleteProof;
681
+ }
682
+ const before = detached.serializeToBytes();
683
+ for (const subStamp of detached.timestamp.directlyVerified()) {
684
+ if (subStamp.isTimestampComplete()) continue;
685
+ for (const att of subStamp.attestations) {
686
+ if (att.kind !== "pending") continue;
687
+ if (!DEFAULT_CALENDAR_WHITELIST.contains(att.uri)) {
688
+ logger?.warn(`Ignoring attestation from non-whitelisted calendar ${att.uri}`);
689
+ continue;
690
+ }
691
+ try {
692
+ const upgraded = await new CalendarClient(att.uri, networkLayer, logger).getTimestamp(
693
+ subStamp.getDigest(),
694
+ signal
695
+ );
696
+ subStamp.merge(upgraded);
697
+ } catch (err) {
698
+ if (err instanceof CommitmentNotFoundError) {
699
+ logger?.debug(`Calendar ${att.uri} has not confirmed yet`);
700
+ continue;
701
+ }
702
+ logger?.warn(
703
+ `Failed to query ${att.uri}: ${err instanceof Error ? err.message : String(err)}`
704
+ );
705
+ }
706
+ }
707
+ }
708
+ const after = detached.serializeToBytes();
709
+ if (bytesEqual(before, after)) {
710
+ throw new UpgradeError("No calendar has confirmed the timestamp yet (Bitcoin not yet mined)");
711
+ }
712
+ return after;
713
+ }
714
+
656
715
  // src/security/ssrf-web.ts
657
716
  async function assertSafeCalendarUrlStructural(url) {
658
717
  let parsed;
@@ -732,6 +791,14 @@ var OpenTimestampsBrowserClient = class {
732
791
  this.minSubs
733
792
  );
734
793
  }
794
+ /**
795
+ * proof: a pending .ots as bytes. Queries the calendars it references (allowlisted) and returns
796
+ * the upgraded .ots. Throws UpgradeError if no calendar has confirmed the timestamp yet, which is
797
+ * the normal state until Bitcoin mines the commitment (typically an hour or more after stamping).
798
+ */
799
+ upgrade(proof) {
800
+ return upgradeProofBytes(proof, this.layer, this.logger);
801
+ }
735
802
  };
736
803
  export {
737
804
  BROWSER_CALENDARS,
package/dist/index.cjs CHANGED
@@ -738,13 +738,20 @@ async function orchestrateStamp(hash, calendars, networkLayer, validateCalendarU
738
738
  return detached.serializeToBytes();
739
739
  }
740
740
 
741
- // src/core/upgrade.ts
741
+ // src/core/upgrade-core.ts
742
742
  var import_core4 = require("@otskit/core");
743
- var bytesEqFast = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)) === 0;
744
- async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, logger, signal) {
743
+ function bytesEqual(a, b) {
744
+ if (a.length !== b.length) return false;
745
+ let diff = 0;
746
+ for (let i = 0; i < a.length; i++) {
747
+ diff |= a[i] ^ b[i];
748
+ }
749
+ return diff === 0;
750
+ }
751
+ async function upgradeProofBytes(incompleteProof, networkLayer, logger, signal) {
745
752
  let detached;
746
753
  try {
747
- detached = import_core4.DetachedTimestampFile.deserialize(new Uint8Array(incompleteProof));
754
+ detached = import_core4.DetachedTimestampFile.deserialize(incompleteProof);
748
755
  } catch (error) {
749
756
  throw new ValidationError("Invalid .ots proof format", {
750
757
  /* v8 ignore next */
@@ -753,7 +760,7 @@ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, log
753
760
  }
754
761
  if (detached.timestamp.isTimestampComplete()) {
755
762
  logger?.info("Proof already complete; nothing to upgrade");
756
- return Buffer.from(incompleteProof);
763
+ return incompleteProof;
757
764
  }
758
765
  const before = detached.serializeToBytes();
759
766
  for (const subStamp of detached.timestamp.directlyVerified()) {
@@ -782,10 +789,21 @@ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, log
782
789
  }
783
790
  }
784
791
  const after = detached.serializeToBytes();
785
- if (bytesEqFast(before, after)) {
792
+ if (bytesEqual(before, after)) {
786
793
  throw new UpgradeError("No calendar has confirmed the timestamp yet (Bitcoin not yet mined)");
787
794
  }
788
- return Buffer.from(after);
795
+ return after;
796
+ }
797
+
798
+ // src/core/upgrade.ts
799
+ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, logger, signal) {
800
+ const upgraded = await upgradeProofBytes(
801
+ new Uint8Array(incompleteProof),
802
+ networkLayer,
803
+ logger,
804
+ signal
805
+ );
806
+ return Buffer.from(upgraded);
789
807
  }
790
808
 
791
809
  // src/core/verify.ts
@@ -885,10 +903,18 @@ var EsploraClient = class {
885
903
  this.#logger?.debug(`Esplora raw header ${hash}`);
886
904
  const response = await this.#networkLayer.request(
887
905
  this.#url,
888
- { url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "application/octet-stream" } },
906
+ { url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "text/plain" } },
889
907
  signal
890
908
  );
891
- const data = response.data;
909
+ const hex = this.#decode(response.data).trim();
910
+ let data;
911
+ try {
912
+ data = hexToBytes(hex);
913
+ } catch (cause) {
914
+ throw new EsploraResponseError("esplora returned a non-hex raw block header", {
915
+ ...cause instanceof Error ? { cause } : {}
916
+ });
917
+ }
892
918
  if (data.length !== RAW_HEADER_SIZE) {
893
919
  throw new EsploraResponseError(
894
920
  `raw block header must be ${RAW_HEADER_SIZE} bytes; got ${data.length}`
@@ -997,12 +1023,7 @@ async function orchestrateVerify(proof, networkLayer, originalDataHash, logger,
997
1023
  for (const { msg, attestation } of bitcoinAtts) {
998
1024
  if (attestation.kind !== "bitcoin") continue;
999
1025
  try {
1000
- const blockTime = await verifyTimestampAttestation(
1001
- Uint8Array.from(msg).reverse(),
1002
- attestation,
1003
- explorer,
1004
- signal
1005
- );
1026
+ const blockTime = await verifyTimestampAttestation(msg, attestation, explorer, signal);
1006
1027
  logger?.info(`Verified against Bitcoin block ${attestation.height}`);
1007
1028
  return { status: "verified", blockHeight: attestation.height, blockTime };
1008
1029
  } catch (err) {
package/dist/index.js CHANGED
@@ -688,13 +688,20 @@ async function orchestrateStamp(hash, calendars, networkLayer, validateCalendarU
688
688
  return detached.serializeToBytes();
689
689
  }
690
690
 
691
- // src/core/upgrade.ts
691
+ // src/core/upgrade-core.ts
692
692
  import { DetachedTimestampFile as DetachedTimestampFile2 } from "@otskit/core";
693
- var bytesEqFast = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)) === 0;
694
- async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, logger, signal) {
693
+ function bytesEqual(a, b) {
694
+ if (a.length !== b.length) return false;
695
+ let diff = 0;
696
+ for (let i = 0; i < a.length; i++) {
697
+ diff |= a[i] ^ b[i];
698
+ }
699
+ return diff === 0;
700
+ }
701
+ async function upgradeProofBytes(incompleteProof, networkLayer, logger, signal) {
695
702
  let detached;
696
703
  try {
697
- detached = DetachedTimestampFile2.deserialize(new Uint8Array(incompleteProof));
704
+ detached = DetachedTimestampFile2.deserialize(incompleteProof);
698
705
  } catch (error) {
699
706
  throw new ValidationError("Invalid .ots proof format", {
700
707
  /* v8 ignore next */
@@ -703,7 +710,7 @@ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, log
703
710
  }
704
711
  if (detached.timestamp.isTimestampComplete()) {
705
712
  logger?.info("Proof already complete; nothing to upgrade");
706
- return Buffer.from(incompleteProof);
713
+ return incompleteProof;
707
714
  }
708
715
  const before = detached.serializeToBytes();
709
716
  for (const subStamp of detached.timestamp.directlyVerified()) {
@@ -732,10 +739,21 @@ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, log
732
739
  }
733
740
  }
734
741
  const after = detached.serializeToBytes();
735
- if (bytesEqFast(before, after)) {
742
+ if (bytesEqual(before, after)) {
736
743
  throw new UpgradeError("No calendar has confirmed the timestamp yet (Bitcoin not yet mined)");
737
744
  }
738
- return Buffer.from(after);
745
+ return after;
746
+ }
747
+
748
+ // src/core/upgrade.ts
749
+ async function orchestrateUpgrade(incompleteProof, _calendars, networkLayer, logger, signal) {
750
+ const upgraded = await upgradeProofBytes(
751
+ new Uint8Array(incompleteProof),
752
+ networkLayer,
753
+ logger,
754
+ signal
755
+ );
756
+ return Buffer.from(upgraded);
739
757
  }
740
758
 
741
759
  // src/core/verify.ts
@@ -835,10 +853,18 @@ var EsploraClient = class {
835
853
  this.#logger?.debug(`Esplora raw header ${hash}`);
836
854
  const response = await this.#networkLayer.request(
837
855
  this.#url,
838
- { url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "application/octet-stream" } },
856
+ { url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "text/plain" } },
839
857
  signal
840
858
  );
841
- const data = response.data;
859
+ const hex = this.#decode(response.data).trim();
860
+ let data;
861
+ try {
862
+ data = hexToBytes(hex);
863
+ } catch (cause) {
864
+ throw new EsploraResponseError("esplora returned a non-hex raw block header", {
865
+ ...cause instanceof Error ? { cause } : {}
866
+ });
867
+ }
842
868
  if (data.length !== RAW_HEADER_SIZE) {
843
869
  throw new EsploraResponseError(
844
870
  `raw block header must be ${RAW_HEADER_SIZE} bytes; got ${data.length}`
@@ -947,12 +973,7 @@ async function orchestrateVerify(proof, networkLayer, originalDataHash, logger,
947
973
  for (const { msg, attestation } of bitcoinAtts) {
948
974
  if (attestation.kind !== "bitcoin") continue;
949
975
  try {
950
- const blockTime = await verifyTimestampAttestation(
951
- Uint8Array.from(msg).reverse(),
952
- attestation,
953
- explorer,
954
- signal
955
- );
976
+ const blockTime = await verifyTimestampAttestation(msg, attestation, explorer, signal);
956
977
  logger?.info(`Verified against Bitcoin block ${attestation.height}`);
957
978
  return { status: "verified", blockHeight: attestation.height, blockTime };
958
979
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otskit/client",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Official client SDK for OpenTimestamps calendars with resilience patterns",
5
5
  "author": "alexalves87",
6
6
  "license": "MIT",