@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 +6 -0
- package/dist/browser.js +67 -0
- package/dist/index.cjs +36 -15
- package/dist/index.js +36 -15
- package/package.json +1 -1
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
|
-
|
|
744
|
-
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
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: "
|
|
906
|
+
{ url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "text/plain" } },
|
|
889
907
|
signal
|
|
890
908
|
);
|
|
891
|
-
const
|
|
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
|
-
|
|
694
|
-
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
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: "
|
|
856
|
+
{ url: `${this.#url}/block/${hash}/header`, method: "GET", headers: { Accept: "text/plain" } },
|
|
839
857
|
signal
|
|
840
858
|
);
|
|
841
|
-
const
|
|
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) {
|