# @otskit/core
[](https://github.com/OTSkit/OTSkit-core/actions/workflows/ci.yml)
[](https://github.com/OTSkit/OTSkit-core/actions/workflows/codeql.yml)
[](https://www.npmjs.com/package/@otskit/core)
[](https://www.npmjs.com/package/@otskit/core)
[](https://www.typescriptlang.org/)
[](https://nodejs.org)
[](https://codecov.io/gh/OTSkit/OTSkit-core)
[](https://sonarcloud.io/summary/new_code?id=OTSkit_OTSkit-core)
[](LICENSE)
OpenTimestamps 核心库 — TypeScript,零依赖,失败即关闭(fail-closed)。
作为 [`@alexalves87/opentimestamps`](https://www.npmjs.com/package/@alexalves87/opentimestamps) 的下一代版本,我们使用 TypeScript 6 严格模式从头重写了它,它具有零外部依赖以及失败即关闭的安全姿态。
## 什么是 OpenTimestamps?
[OpenTimestamps](https://opentimestamps.org) 是一个用于去中心化时间戳证明的开放标准。它允许你通过将文档的加密哈希值锚定到 Bitcoin(或 Litecoin)区块链中,从而证明该文档在特定时间点已经存在。证明文件存储在紧凑的 `.ots` 文件中 —— 验证过程永远不需要可信的第三方参与。
## 功能
- **零依赖** — 纯 TypeScript 实现,无 npm 供应链风险
- **设计上失败即关闭** — 严格的输入验证;默认拒绝 `ArrayBuffer`、`Buffer` 以及任何格式错误的输入
- **TypeScript 优先** — 完整的类型声明,TypeScript 6 严格模式,ESM + CJS 双构建
- **完整的协议** — 创建、序列化、反序列化、合并和验证时间戳
- **Merkle 树支持** — 在单个区块链交易中对数千个文档进行批量时间戳标记
- **纯密码学实现** — 内置 SHA1、SHA256 和 RIPEMD-160,无需原生绑定
## 安装
```
npm install @otskit/core
```
需要 Node.js ≥ 20。
## 快速开始
### 为文件添加时间戳
```
import { DetachedTimestampFile, OpSHA256, makePending } from '@otskit/core';
import { readFileSync, writeFileSync } from 'node:fs';
// Hash your file and wrap it in a detached timestamp
const fileContent = new Uint8Array(readFileSync('document.pdf'));
const dtf = DetachedTimestampFile.fromBytes(new OpSHA256(), fileContent);
// Register with an OpenTimestamps calendar (pending — will be upgraded to Bitcoin)
dtf.timestamp.attestations.push(
makePending('https://alice.btc.calendar.opentimestamps.org'),
);
// Save the .ots proof file alongside the original document
writeFileSync('document.pdf.ots', dtf.serializeToBytes());
```
### 读取 .ots 文件
```
import { DetachedTimestampFile } from '@otskit/core';
import { readFileSync } from 'node:fs';
const otsBytes = new Uint8Array(readFileSync('document.pdf.ots'));
const dtf = DetachedTimestampFile.deserialize(otsBytes);
console.log('Hash algorithm:', dtf.fileHashOp.tagName);
console.log('File digest: ', Buffer.from(dtf.fileDigest()).toString('hex'));
console.log('Complete: ', dtf.timestamp.isTimestampComplete());
console.log('Attestations: ', dtf.timestamp.getAttestations());
```
### 对比 Bitcoin 区块头进行验证
```
import { DetachedTimestampFile, verifyAgainstBlockheader } from '@otskit/core';
import { readFileSync } from 'node:fs';
const dtf = DetachedTimestampFile.deserialize(
new Uint8Array(readFileSync('document.pdf.ots')),
);
// Retrieve the block header from a full node or trusted block explorer
for (const { msg, attestation } of dtf.timestamp.allAttestations()) {
if (attestation.kind === 'bitcoin') {
verifyAgainstBlockheader(msg, blockHeader); // throws VerificationError if invalid
console.log(`Verified at Bitcoin block height ${attestation.height}`);
}
}
```
### 使用 Merkle 树进行批量时间戳标记
在单个区块链交易中锚定数千个文档:
```
import { DetachedTimestampFile, OpSHA256, makeMerkleTree, makePending } from '@otskit/core';
import { readFileSync, writeFileSync } from 'node:fs';
const files = ['a.pdf', 'b.pdf', 'c.pdf'];
const op = new OpSHA256();
const dtfs = files.map(f =>
DetachedTimestampFile.fromBytes(op, new Uint8Array(readFileSync(f))),
);
// One Merkle root covers all documents — one calendar call, one blockchain entry
const root = makeMerkleTree(dtfs.map(d => d.timestamp));
root.attestations.push(makePending('https://alice.btc.calendar.opentimestamps.org'));
// Each .ots file carries its own path to the shared root
files.forEach((f, i) => writeFileSync(`${f}.ots`, dtfs[i]!.serializeToBytes()));
```
## API 参考
### `DetachedTimestampFile`
`.ots` 证明文件的不可变包装器。
| 成员 | 描述 |
|--------|-------------|
| `DetachedTimestampFile.fromBytes(op, content)` | 从原始文件字节创建 |
| `DetachedTimestampFile.fromHash(op, digest)` | 从预计算的摘要创建 |
| `DetachedTimestampFile.deserialize(bytes)` | 解析 `.ots` 文件(仅限 `Uint8Array` — 失败即关闭) |
| `.serializeToBytes()` | 序列化回 `.ots` 字节 |
| `.fileDigest()` | 以 `Uint8Array` 形式返回文件的哈希值(防御性拷贝) |
| `.fileHashOp` | 用于哈希文件的 `CryptOp` |
| `.timestamp` | 证明树的根 `Timestamp` |
### `Timestamp`
证明树中的一个节点。每个节点持有一个摘要(`msg`)、直接证明以及指向子时间戳的操作分支。
| 成员 | 描述 |
|--------|-------------|
| `new Timestamp(msg)` | 创建一个新的叶节点 |
| `.add(op)` | 应用一个操作并返回(或复用)子时间戳 |
| `.addExisting(op, stamp)` | 交叉链接到现有时间戳(由 Merkle 内部使用) |
| `.merge(other)` | 从具有相同 `msg` 的另一个时间戳中吸收证明和分支 |
| `.attestations` | 直接的 `Attestation[]` — push 以向此节点添加密封 |
| `.getDigest()` | `msg` 的防御性拷贝 |
| `.getAttestations()` | 树中任意位置的所有证明 |
| `.allAttestations()` | 树中所有的 `{ msg, attestation }` 对 |
| `.isTimestampComplete()` | 如果存在 Bitcoin 或 Litecoin 证明,则返回 `true` |
| `.allTips()` | 叶子摘要(没有操作的节点) |
| `.equals(other)` | 深度结构相等性比较 |
### 操作
所有操作都继承自 `Op` 并且是可序列化的。二元操作(`OpAppend`、`OpPrepend`)在其构造函数中接收一个 `Uint8Array` 参数。
| 类 | 标签 | 描述 |
|-------|-----|-------------|
| `OpSHA256` | `0x08` | SHA-256 哈希 |
| `OpSHA1` | `0x02` | SHA-1 哈希 |
| `OpRIPEMD160` | `0x03` | RIPEMD-160 哈希 |
| `OpAppend(suffix)` | `0xf0` | 追加字节 |
| `OpPrepend(prefix)` | `0xf1` | 前置字节 |
| `OpReverse` | `0xf2` | 反转字节顺序 |
| `OpHexlify` | `0xf3` | 编码为 ASCII 十六进制 |
### 证明
| 函数 | 描述 |
|----------|-------------|
| `makePending(uri)` | 待处理日历证明 — 证明尚未升级到区块链 |
| `makeBitcoin(height)` | 在区块 `height` 处的 Bitcoin 区块链证明 |
| `makeLitecoin(height)` | 在区块 `height` 处的 Litecoin 区块链证明 |
| `verifyAgainstBlockheader(digest, header)` | 根据 Maurkle 根验证 32 字节的摘要 — 失败时抛出 `VerificationError` |
### Merkle 树
| 函数 | 描述 |
|----------|-------------|
| `makeMerkleTree(timestamps)` | 从时间戳数组构建 Merkle-Mountain-Range。返回根节点。如果输入为空,则抛出 `EmptyMerkleTreeError`。 |
### 序列化上下文
用于遵循 OpenTimestamps 线级格式的底层二进制 I/O(LEB128 变长整数,长度前缀字节):
| 类 | 描述 |
|-------|-------------|
| `StreamSerializationContext` | 写入二进制数据;调用 `.getOutput()` 获取结果 |
| `StreamDeserializationContext` | 读取带有边界检查和 EOF 强制执行的二进制数据 |
### 实用工具
| 函数 | 描述 |
|----------|-------------|
| `hexToBytes(hex)` | 十六进制字符串 → `Uint8Array` |
| `bytesToHex(bytes)` | `Uint8Array` → 小写十六进制字符串 |
| `textToBytes(text)` | UTF-8 字符串 → `Uint8Array` |
| `bytesToText(bytes)` | `Uint8Array` → UTF-8 字符串(遇到无效序列时抛出异常) |
| `randBytes(n)` | 通过 `crypto.getRandomValues` 生成加密安全的随机字节 — 如果不可用则抛出异常 |
### 错误
所有错误类都继承自 `Error`,并按名称导出以便进行精确的 `catch` 处理。
| 类别 | 错误类 |
|----------|--------------|
| 反序列化 | `DeserializationError`, `BadMagicError`, `TruncatedStreamError`, `OversizedDataError`, `VaruintOverflowError`, `TrailingGarbageError`, `UnknownOperationError`, `InvalidUriError`, `UnsupportedVersionError` |
| 操作 | `OpExecutionError`, `MessageTooLongError`, `ResultTooLongError` |
| 验证 | `VerificationError` |
| 树 | `EmptyTimestampError`, `MergeError`, `EmptyMerkleTreeError` |
## 许可证