@bcts/provenance-mark 1.0.0-alpha.8 → 1.0.0-beta.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/LICENSE +2 -1
- package/README.md +1 -1
- package/dist/index.cjs +1174 -584
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +489 -136
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +489 -136
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +1247 -659
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +1132 -563
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -22
- package/src/crypto-utils.ts +9 -3
- package/src/date.ts +42 -0
- package/src/envelope.ts +122 -0
- package/src/error.ts +21 -0
- package/src/generator.ts +153 -2
- package/src/index.ts +24 -0
- package/src/mark-info.ts +38 -17
- package/src/mark.ts +399 -45
- package/src/resolution.ts +32 -9
- package/src/rng-state.ts +6 -0
- package/src/seed.ts +6 -0
- package/src/utils.ts +68 -39
- package/src/validate.ts +63 -57
- package/src/xoshiro256starstar.ts +6 -0
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bcts/provenance-mark",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Blockchain Commons Provenance Mark for TypeScript - A cryptographically-secured system for establishing and verifying the authenticity of works",
|
|
6
6
|
"license": "BSD-2-Clause-Patent",
|
|
7
|
-
"author": "
|
|
8
|
-
"homepage": "https://
|
|
7
|
+
"author": "Parity Technologies <admin@parity.io> (https://parity.io)",
|
|
8
|
+
"homepage": "https://bcts.dev",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "https://github.com/paritytech/bcts",
|
|
12
12
|
"directory": "packages/provenance-mark"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/paritytech/bcts/issues"
|
|
16
16
|
},
|
|
17
17
|
"main": "dist/index.cjs",
|
|
18
18
|
"module": "dist/index.mjs",
|
|
@@ -33,11 +33,10 @@
|
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsdown",
|
|
36
|
-
"dev": "tsdown --watch",
|
|
37
36
|
"test": "vitest run",
|
|
38
37
|
"test:watch": "vitest",
|
|
39
|
-
"lint": "eslint 'src/**/*.ts'",
|
|
40
|
-
"lint:fix": "eslint 'src/**/*.ts' --fix",
|
|
38
|
+
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
|
|
39
|
+
"lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
|
|
41
40
|
"typecheck": "tsc --noEmit",
|
|
42
41
|
"clean": "rm -rf dist",
|
|
43
42
|
"docs": "typedoc",
|
|
@@ -57,22 +56,23 @@
|
|
|
57
56
|
"devDependencies": {
|
|
58
57
|
"@bcts/eslint": "^0.1.0",
|
|
59
58
|
"@bcts/tsconfig": "^0.1.0",
|
|
60
|
-
"@eslint/js": "^
|
|
61
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
62
|
-
"@typescript-eslint/parser": "^8.
|
|
63
|
-
"eslint": "^
|
|
59
|
+
"@eslint/js": "^10.0.1",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
|
61
|
+
"@typescript-eslint/parser": "^8.59.0",
|
|
62
|
+
"eslint": "^10.2.1",
|
|
64
63
|
"ts-node": "^10.9.2",
|
|
65
|
-
"tsdown": "^0.
|
|
66
|
-
"typedoc": "^0.28.
|
|
67
|
-
"typescript": "^
|
|
68
|
-
"vitest": "^
|
|
64
|
+
"tsdown": "^0.21.0",
|
|
65
|
+
"typedoc": "^0.28.19",
|
|
66
|
+
"typescript": "^6.0.3",
|
|
67
|
+
"vitest": "^4.1.5"
|
|
69
68
|
},
|
|
70
69
|
"dependencies": {
|
|
71
|
-
"@bcts/dcbor": "^1.0.0-
|
|
72
|
-
"@bcts/
|
|
73
|
-
"@bcts/
|
|
74
|
-
"@bcts/
|
|
75
|
-
"@
|
|
76
|
-
"@noble/
|
|
70
|
+
"@bcts/dcbor": "^1.0.0-beta.0",
|
|
71
|
+
"@bcts/envelope": "^1.0.0-beta.0",
|
|
72
|
+
"@bcts/rand": "^1.0.0-beta.0",
|
|
73
|
+
"@bcts/tags": "^1.0.0-beta.0",
|
|
74
|
+
"@bcts/uniform-resources": "^1.0.0-beta.0",
|
|
75
|
+
"@noble/ciphers": "^2.2.0",
|
|
76
|
+
"@noble/hashes": "^2.2.0"
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/crypto-utils.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust/src/crypto_utils.rs
|
|
2
8
|
|
|
3
|
-
import { sha256 as sha256Hash } from "@noble/hashes/
|
|
4
|
-
import { hkdf } from "@noble/hashes/hkdf";
|
|
5
|
-
import { chacha20 } from "@noble/ciphers/chacha";
|
|
9
|
+
import { sha256 as sha256Hash } from "@noble/hashes/sha2.js";
|
|
10
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
11
|
+
import { chacha20 } from "@noble/ciphers/chacha.js";
|
|
6
12
|
|
|
7
13
|
export const SHA256_SIZE = 32;
|
|
8
14
|
|
package/src/date.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust/src/date.rs
|
|
2
8
|
|
|
3
9
|
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
@@ -214,3 +220,39 @@ export function dateToDateString(date: Date): string {
|
|
|
214
220
|
const day = date.getUTCDate().toString().padStart(2, "0");
|
|
215
221
|
return `${year}-${month}-${day}`;
|
|
216
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Renders a `Date` the way Rust's `dcbor::Date::Display` does
|
|
226
|
+
* (`bc-dcbor-rust/src/date.rs:485-492`):
|
|
227
|
+
*
|
|
228
|
+
* - When the UTC time is exactly `00:00:00` (subsecond precision is
|
|
229
|
+
* ignored — Rust's check is `hour == 0 && minute == 0 && second == 0`,
|
|
230
|
+
* matching `chrono::SecondsFormat::Secs`), emit just `YYYY-MM-DD`.
|
|
231
|
+
* - Otherwise emit RFC 3339 with second precision (no fractional
|
|
232
|
+
* seconds), e.g. `2023-02-08T15:30:45Z`.
|
|
233
|
+
*
|
|
234
|
+
* This is the canonical "Rust string" for dates across the
|
|
235
|
+
* provenance-mark public surface — `mark.toDebugString`,
|
|
236
|
+
* `mark.precedesOpt` `DateOrdering` issue, `markdownSummary`, and the
|
|
237
|
+
* validation report's `DateOrdering` payload all use it. Centralising
|
|
238
|
+
* here keeps every call site in lockstep with the Rust output.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* dateToDisplay(new Date("2023-06-20T00:00:00Z")); // "2023-06-20"
|
|
243
|
+
* dateToDisplay(new Date("2023-06-20T15:30:45Z")); // "2023-06-20T15:30:45Z"
|
|
244
|
+
* dateToDisplay(new Date("2023-06-20T15:30:45.123Z")); // "2023-06-20T15:30:45Z"
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export function dateToDisplay(date: Date): string {
|
|
248
|
+
const hasTime =
|
|
249
|
+
date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0 || date.getUTCSeconds() !== 0;
|
|
250
|
+
if (!hasTime) {
|
|
251
|
+
// Midnight (subsecond precision ignored) — show only the date.
|
|
252
|
+
return dateToDateString(date);
|
|
253
|
+
}
|
|
254
|
+
// Full RFC 3339, second precision. JS `toISOString()` always emits
|
|
255
|
+
// millisecond precision (`.NNNZ`); strip the fractional component to
|
|
256
|
+
// match Rust's `SecondsFormat::Secs`.
|
|
257
|
+
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
258
|
+
}
|
package/src/envelope.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* Envelope support for Provenance Marks
|
|
7
|
+
*
|
|
8
|
+
* This module provides Gordian Envelope integration for ProvenanceMark and
|
|
9
|
+
* ProvenanceMarkGenerator, enabling them to be used with the bc-envelope
|
|
10
|
+
* ecosystem.
|
|
11
|
+
*
|
|
12
|
+
* Ported from provenance-mark-rust/src/mark.rs and generator.rs (envelope feature)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type Envelope,
|
|
17
|
+
type FormatContext,
|
|
18
|
+
withFormatContextMut,
|
|
19
|
+
registerTagsIn as envelopeRegisterTagsIn,
|
|
20
|
+
} from "@bcts/envelope";
|
|
21
|
+
import { type Cbor, type SummarizerResult } from "@bcts/dcbor";
|
|
22
|
+
import { PROVENANCE_MARK } from "@bcts/tags";
|
|
23
|
+
import { ProvenanceMark } from "./mark.js";
|
|
24
|
+
import { ProvenanceMarkGenerator } from "./generator.js";
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Tag Registration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registers provenance mark tags in the global format context.
|
|
32
|
+
*
|
|
33
|
+
* Matches Rust: register_tags()
|
|
34
|
+
*/
|
|
35
|
+
export function registerTags(): void {
|
|
36
|
+
withFormatContextMut((context) => {
|
|
37
|
+
registerTagsIn(context);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Registers provenance mark tags in a specific format context.
|
|
43
|
+
*
|
|
44
|
+
* Matches Rust: register_tags_in()
|
|
45
|
+
*
|
|
46
|
+
* @param context - The format context to register tags in
|
|
47
|
+
*/
|
|
48
|
+
export function registerTagsIn(context: FormatContext): void {
|
|
49
|
+
envelopeRegisterTagsIn(context);
|
|
50
|
+
|
|
51
|
+
context
|
|
52
|
+
.tags()
|
|
53
|
+
.setSummarizer(
|
|
54
|
+
BigInt(PROVENANCE_MARK.value),
|
|
55
|
+
(untaggedCbor: Cbor, _flat: boolean): SummarizerResult => {
|
|
56
|
+
try {
|
|
57
|
+
const mark = ProvenanceMark.fromUntaggedCbor(untaggedCbor);
|
|
58
|
+
return { ok: true, value: mark.toString() };
|
|
59
|
+
} catch {
|
|
60
|
+
return { ok: false, error: { type: "Custom", message: "invalid provenance mark" } };
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// ProvenanceMark Envelope Support
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert a ProvenanceMark to an Envelope.
|
|
72
|
+
*
|
|
73
|
+
* Delegates to ProvenanceMark.intoEnvelope() — single source of truth.
|
|
74
|
+
*
|
|
75
|
+
* @param mark - The provenance mark to convert
|
|
76
|
+
* @returns An envelope containing the mark
|
|
77
|
+
*/
|
|
78
|
+
export function provenanceMarkToEnvelope(mark: ProvenanceMark): Envelope {
|
|
79
|
+
return mark.intoEnvelope();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract a ProvenanceMark from an Envelope.
|
|
84
|
+
*
|
|
85
|
+
* Delegates to ProvenanceMark.fromEnvelope() — single source of truth.
|
|
86
|
+
*
|
|
87
|
+
* @param envelope - The envelope to extract from
|
|
88
|
+
* @returns The extracted provenance mark
|
|
89
|
+
* @throws ProvenanceMarkError if extraction fails
|
|
90
|
+
*/
|
|
91
|
+
export function provenanceMarkFromEnvelope(envelope: Envelope): ProvenanceMark {
|
|
92
|
+
return ProvenanceMark.fromEnvelope(envelope);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// ProvenanceMarkGenerator Envelope Support
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convert a ProvenanceMarkGenerator to an Envelope.
|
|
101
|
+
*
|
|
102
|
+
* Delegates to ProvenanceMarkGenerator.intoEnvelope() — single source of truth.
|
|
103
|
+
*
|
|
104
|
+
* @param generator - The generator to convert
|
|
105
|
+
* @returns An envelope containing the generator
|
|
106
|
+
*/
|
|
107
|
+
export function provenanceMarkGeneratorToEnvelope(generator: ProvenanceMarkGenerator): Envelope {
|
|
108
|
+
return generator.intoEnvelope();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Extract a ProvenanceMarkGenerator from an Envelope.
|
|
113
|
+
*
|
|
114
|
+
* Delegates to ProvenanceMarkGenerator.fromEnvelope() — single source of truth.
|
|
115
|
+
*
|
|
116
|
+
* @param envelope - The envelope to extract from
|
|
117
|
+
* @returns The extracted generator
|
|
118
|
+
* @throws ProvenanceMarkError if extraction fails
|
|
119
|
+
*/
|
|
120
|
+
export function provenanceMarkGeneratorFromEnvelope(envelope: Envelope): ProvenanceMarkGenerator {
|
|
121
|
+
return ProvenanceMarkGenerator.fromEnvelope(envelope);
|
|
122
|
+
}
|
package/src/error.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust/src/error.rs
|
|
2
8
|
|
|
3
9
|
/**
|
|
@@ -50,6 +56,19 @@ export enum ProvenanceMarkErrorType {
|
|
|
50
56
|
IntegerConversionError = "IntegerConversionError",
|
|
51
57
|
/** Validation error */
|
|
52
58
|
ValidationError = "ValidationError",
|
|
59
|
+
/**
|
|
60
|
+
* Envelope serialization/deserialization error.
|
|
61
|
+
*
|
|
62
|
+
* Mirrors Rust `Error::Envelope(...)`
|
|
63
|
+
* (`provenance-mark-rust/src/error.rs`). The Rust enum surfaces
|
|
64
|
+
* envelope-format failures as their own variant; in earlier
|
|
65
|
+
* revisions of this port they collapsed into `CborError`. Both
|
|
66
|
+
* shapes are still emitted in practice (CBOR errors during envelope
|
|
67
|
+
* round-trip stay as `CborError`); this variant exists for the
|
|
68
|
+
* structural-level mismatches the Rust port tags as
|
|
69
|
+
* `Error::Envelope`.
|
|
70
|
+
*/
|
|
71
|
+
EnvelopeError = "EnvelopeError",
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
/**
|
|
@@ -129,6 +148,8 @@ export class ProvenanceMarkError extends Error {
|
|
|
129
148
|
return `integer conversion error: ${d("message")}`;
|
|
130
149
|
case ProvenanceMarkErrorType.ValidationError:
|
|
131
150
|
return `validation error: ${d("message")}`;
|
|
151
|
+
case ProvenanceMarkErrorType.EnvelopeError:
|
|
152
|
+
return `envelope error: ${d("message")}`;
|
|
132
153
|
default:
|
|
133
154
|
return type;
|
|
134
155
|
}
|
package/src/generator.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust/src/generator.rs
|
|
2
8
|
|
|
3
9
|
import { toBase64, fromBase64, bytesToHex } from "./utils.js";
|
|
4
10
|
import { type Cbor } from "@bcts/dcbor";
|
|
11
|
+
import { Envelope } from "@bcts/envelope";
|
|
5
12
|
|
|
6
13
|
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
7
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type ProvenanceMarkResolution,
|
|
16
|
+
linkLength,
|
|
17
|
+
resolutionToNumber,
|
|
18
|
+
resolutionFromCbor,
|
|
19
|
+
} from "./resolution.js";
|
|
8
20
|
import { ProvenanceSeed } from "./seed.js";
|
|
9
21
|
import { RngState } from "./rng-state.js";
|
|
10
22
|
import { sha256 } from "./crypto-utils.js";
|
|
@@ -150,9 +162,24 @@ export class ProvenanceMarkGenerator {
|
|
|
150
162
|
|
|
151
163
|
/**
|
|
152
164
|
* String representation.
|
|
165
|
+
*
|
|
166
|
+
* Mirrors Rust `Display for ProvenanceMarkGenerator`
|
|
167
|
+
* (`provenance-mark-rust/src/generator.rs:135-147`):
|
|
168
|
+
*
|
|
169
|
+
* ```rust
|
|
170
|
+
* write!(f, "ProvenanceMarkGenerator(chainID: {}, res: {}, seed: {}, nextSeq: {}, rngState: {:?})",
|
|
171
|
+
* hex::encode(&self.chain_id), self.res, self.seed.hex(), self.next_seq, self.rng_state)
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* The `rngState` field uses Rust's `{:?}` (Debug) format, which on a
|
|
175
|
+
* `RngState([u8; 32])` tuple struct produces `RngState([n0, n1, ...])`
|
|
176
|
+
* with each byte rendered as a decimal integer. Earlier revisions of
|
|
177
|
+
* this port omitted `rngState` entirely from `toString()`, so the
|
|
178
|
+
* output diverged from Rust's `Display`.
|
|
153
179
|
*/
|
|
154
180
|
toString(): string {
|
|
155
|
-
|
|
181
|
+
const rngBytes = Array.from(this._rngState.toBytes()).join(", ");
|
|
182
|
+
return `ProvenanceMarkGenerator(chainID: ${bytesToHex(this._chainId)}, res: ${this._res}, seed: ${this._seed.hex()}, nextSeq: ${this._nextSeq}, rngState: RngState([${rngBytes}]))`;
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
/**
|
|
@@ -179,4 +206,128 @@ export class ProvenanceMarkGenerator {
|
|
|
179
206
|
const rngState = RngState.fromBytes(fromBase64(json["rngState"] as string));
|
|
180
207
|
return ProvenanceMarkGenerator.new(res, seed, chainId, nextSeq, rngState);
|
|
181
208
|
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Envelope Support (EnvelopeEncodable)
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Convert this generator to a Gordian Envelope.
|
|
216
|
+
*
|
|
217
|
+
* The envelope contains structured assertions for all generator fields:
|
|
218
|
+
* - isA: "provenance-generator"
|
|
219
|
+
* - res: The resolution
|
|
220
|
+
* - seed: The seed
|
|
221
|
+
* - next-seq: The next sequence number
|
|
222
|
+
* - rng-state: The RNG state
|
|
223
|
+
*
|
|
224
|
+
* Note: Use provenanceMarkGeneratorToEnvelope() for a standalone function alternative.
|
|
225
|
+
*/
|
|
226
|
+
intoEnvelope(): Envelope {
|
|
227
|
+
// Create envelope with chain ID as subject
|
|
228
|
+
let envelope = Envelope.new(this._chainId);
|
|
229
|
+
|
|
230
|
+
// Add type assertion (using addType() which uses IS_A KnownValue, like Rust's add_type())
|
|
231
|
+
envelope = envelope.addType("provenance-generator");
|
|
232
|
+
|
|
233
|
+
// Add resolution
|
|
234
|
+
envelope = envelope.addAssertion("res", resolutionToNumber(this._res));
|
|
235
|
+
|
|
236
|
+
// Add seed
|
|
237
|
+
envelope = envelope.addAssertion("seed", this._seed.toBytes());
|
|
238
|
+
|
|
239
|
+
// Add next sequence number
|
|
240
|
+
envelope = envelope.addAssertion("next-seq", this._nextSeq);
|
|
241
|
+
|
|
242
|
+
// Add RNG state
|
|
243
|
+
envelope = envelope.addAssertion("rng-state", this._rngState.toBytes());
|
|
244
|
+
|
|
245
|
+
return envelope;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extract a ProvenanceMarkGenerator from a Gordian Envelope.
|
|
250
|
+
*
|
|
251
|
+
* @param envelope - The envelope to extract from
|
|
252
|
+
* @returns The extracted generator
|
|
253
|
+
* @throws ProvenanceMarkError if extraction fails
|
|
254
|
+
*/
|
|
255
|
+
static fromEnvelope(envelope: Envelope): ProvenanceMarkGenerator {
|
|
256
|
+
type EnvelopeExt = Envelope & {
|
|
257
|
+
asByteString(): Uint8Array | undefined;
|
|
258
|
+
hasType(t: string): boolean;
|
|
259
|
+
assertionsWithPredicate(p: string): Envelope[];
|
|
260
|
+
subject(): Envelope;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const env = envelope as EnvelopeExt;
|
|
264
|
+
|
|
265
|
+
// Check type
|
|
266
|
+
if (!env.hasType("provenance-generator")) {
|
|
267
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
268
|
+
message: "Envelope is not a provenance-generator",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Extract chain ID from subject
|
|
273
|
+
const subject = env.subject() as EnvelopeExt;
|
|
274
|
+
const chainId = subject.asByteString();
|
|
275
|
+
if (chainId === undefined) {
|
|
276
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
277
|
+
message: "Could not extract chain ID",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Helper to extract assertion object value
|
|
282
|
+
const extractAssertion = (predicate: string): { cbor: Cbor; bytes: Uint8Array | undefined } => {
|
|
283
|
+
const assertions = env.assertionsWithPredicate(predicate);
|
|
284
|
+
if (assertions.length === 0) {
|
|
285
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
286
|
+
message: `Missing ${predicate} assertion`,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const assertionCase = assertions[0].case();
|
|
290
|
+
if (assertionCase.type !== "assertion") {
|
|
291
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
292
|
+
message: `Invalid ${predicate} assertion`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const obj = assertionCase.assertion.object() as EnvelopeExt;
|
|
296
|
+
const objCase = obj.case();
|
|
297
|
+
if (objCase.type === "leaf") {
|
|
298
|
+
return { cbor: objCase.cbor, bytes: obj.asByteString() };
|
|
299
|
+
}
|
|
300
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
301
|
+
message: `Invalid ${predicate} value`,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Extract resolution
|
|
306
|
+
const resValue = extractAssertion("res");
|
|
307
|
+
const res = resolutionFromCbor(resValue.cbor);
|
|
308
|
+
|
|
309
|
+
// Extract seed
|
|
310
|
+
const seedValue = extractAssertion("seed");
|
|
311
|
+
if (seedValue.bytes === undefined) {
|
|
312
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
313
|
+
message: "Invalid seed data",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const seed = ProvenanceSeed.fromBytes(seedValue.bytes);
|
|
317
|
+
|
|
318
|
+
// Extract next-seq
|
|
319
|
+
const seqValue = extractAssertion("next-seq");
|
|
320
|
+
const nextSeq = Number(seqValue.cbor);
|
|
321
|
+
|
|
322
|
+
// Extract rng-state
|
|
323
|
+
const rngValue = extractAssertion("rng-state");
|
|
324
|
+
if (rngValue.bytes === undefined) {
|
|
325
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.CborError, undefined, {
|
|
326
|
+
message: "Invalid rng-state data",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const rngState = RngState.fromBytes(rngValue.bytes);
|
|
330
|
+
|
|
331
|
+
return ProvenanceMarkGenerator.new(res, seed, chainId, nextSeq, rngState);
|
|
332
|
+
}
|
|
182
333
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust
|
|
2
8
|
|
|
3
9
|
// Error types
|
|
@@ -44,8 +50,13 @@ export {
|
|
|
44
50
|
dateToIso8601,
|
|
45
51
|
dateFromIso8601,
|
|
46
52
|
dateToDateString,
|
|
53
|
+
dateToDisplay,
|
|
47
54
|
} from "./date.js";
|
|
48
55
|
|
|
56
|
+
// User-facing parsers (mirroring Rust `util::parse_seed` /
|
|
57
|
+
// `util::parse_date`).
|
|
58
|
+
export { parseSeed, parseDate } from "./utils.js";
|
|
59
|
+
|
|
49
60
|
// Crypto utilities
|
|
50
61
|
export {
|
|
51
62
|
SHA256_SIZE,
|
|
@@ -88,3 +99,16 @@ export {
|
|
|
88
99
|
|
|
89
100
|
// Mark Info
|
|
90
101
|
export { ProvenanceMarkInfo } from "./mark-info.js";
|
|
102
|
+
|
|
103
|
+
// Envelope support
|
|
104
|
+
export {
|
|
105
|
+
registerTags,
|
|
106
|
+
registerTagsIn,
|
|
107
|
+
provenanceMarkToEnvelope,
|
|
108
|
+
provenanceMarkFromEnvelope,
|
|
109
|
+
provenanceMarkGeneratorToEnvelope,
|
|
110
|
+
provenanceMarkGeneratorFromEnvelope,
|
|
111
|
+
} from "./envelope.js";
|
|
112
|
+
|
|
113
|
+
// Re-export FormatContext for registerTagsIn callers
|
|
114
|
+
export { FormatContext } from "@bcts/envelope";
|
package/src/mark-info.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
3
|
+
* Copyright © 2025-2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
// Ported from provenance-mark-rust/src/mark_info.rs
|
|
2
8
|
|
|
3
|
-
import { UR } from "@bcts/uniform-resources";
|
|
4
|
-
import { decodeCbor, cborData } from "@bcts/dcbor";
|
|
5
|
-
import { PROVENANCE_MARK } from "@bcts/tags";
|
|
9
|
+
import type { UR } from "@bcts/uniform-resources";
|
|
6
10
|
|
|
7
11
|
import { ProvenanceMark } from "./mark.js";
|
|
12
|
+
import { dateToDisplay } from "./date.js";
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* Wrapper for a provenance mark with additional display information.
|
|
@@ -32,16 +37,21 @@ export class ProvenanceMarkInfo {
|
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Create a new ProvenanceMarkInfo from a mark.
|
|
40
|
+
*
|
|
41
|
+
* Mirrors Rust `ProvenanceMarkInfo::new`
|
|
42
|
+
* (`provenance-mark-rust/src/mark_info.rs`), which calls
|
|
43
|
+
* `mark.ur()` — i.e. the `UREncodable` implementation, whose
|
|
44
|
+
* payload is the **untagged** CBOR with type `"provenance"`. Earlier
|
|
45
|
+
* revisions of this port called `decodeCbor(mark.toCborData())` and
|
|
46
|
+
* wrapped the resulting *tagged* CBOR in `UR.new("provenance", ...)`,
|
|
47
|
+
* which prepended the CBOR tag to the UR bytewords and broke
|
|
48
|
+
* cross-impl interop (UR strings produced by Rust would not parse,
|
|
49
|
+
* and vice versa).
|
|
35
50
|
*/
|
|
36
51
|
static new(mark: ProvenanceMark, comment = ""): ProvenanceMarkInfo {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
const cborValue = decodeCbor(mark.toCborData());
|
|
42
|
-
const ur = UR.new(tagName, cborValue);
|
|
43
|
-
const bytewords = mark.bytewordsIdentifier(true);
|
|
44
|
-
const bytemoji = mark.bytemojiIdentifier(true);
|
|
52
|
+
const ur = mark.ur();
|
|
53
|
+
const bytewords = mark.idBytewords(4, true);
|
|
54
|
+
const bytemoji = mark.idBytemoji(4, true);
|
|
45
55
|
return new ProvenanceMarkInfo(mark, ur, bytewords, bytemoji, comment);
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -67,6 +77,10 @@ export class ProvenanceMarkInfo {
|
|
|
67
77
|
|
|
68
78
|
/**
|
|
69
79
|
* Generate a markdown summary of the mark.
|
|
80
|
+
*
|
|
81
|
+
* Date rendering uses {@link dateToDisplay} so midnight-UTC dates
|
|
82
|
+
* appear as `YYYY-MM-DD` (matching Rust `format!("{}",
|
|
83
|
+
* self.mark.date())`), not as `YYYY-MM-DDT00:00:00Z`.
|
|
70
84
|
*/
|
|
71
85
|
markdownSummary(): string {
|
|
72
86
|
const lines: string[] = [];
|
|
@@ -74,7 +88,7 @@ export class ProvenanceMarkInfo {
|
|
|
74
88
|
lines.push("---");
|
|
75
89
|
|
|
76
90
|
lines.push("");
|
|
77
|
-
lines.push(this._mark.date()
|
|
91
|
+
lines.push(dateToDisplay(this._mark.date()));
|
|
78
92
|
|
|
79
93
|
lines.push("");
|
|
80
94
|
lines.push(`#### ${this._ur.toString()}`);
|
|
@@ -95,29 +109,36 @@ export class ProvenanceMarkInfo {
|
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
/**
|
|
98
|
-
* JSON serialization.
|
|
112
|
+
* JSON serialization. Field order mirrors Rust's `#[derive(Serialize)]`
|
|
113
|
+
* on `ProvenanceMarkInfo` (provenance-mark-rust/src/mark_info.rs):
|
|
114
|
+
* `ur, bytewords, bytemoji, [comment,] mark` — `comment` (when present)
|
|
115
|
+
* comes BEFORE `mark`. Rust uses `skip_serializing_if = "String::is_empty"`,
|
|
116
|
+
* matched here by the `if (...length > 0)` guard.
|
|
99
117
|
*/
|
|
100
118
|
toJSON(): Record<string, unknown> {
|
|
101
119
|
const result: Record<string, unknown> = {
|
|
102
120
|
ur: this._ur.toString(),
|
|
103
121
|
bytewords: this._bytewords,
|
|
104
122
|
bytemoji: this._bytemoji,
|
|
105
|
-
mark: this._mark.toJSON(),
|
|
106
123
|
};
|
|
107
124
|
if (this._comment.length > 0) {
|
|
108
125
|
result["comment"] = this._comment;
|
|
109
126
|
}
|
|
127
|
+
result["mark"] = this._mark.toJSON();
|
|
110
128
|
return result;
|
|
111
129
|
}
|
|
112
130
|
|
|
113
131
|
/**
|
|
114
132
|
* Create from JSON object.
|
|
133
|
+
*
|
|
134
|
+
* Decodes the UR string through {@link ProvenanceMark.fromURString},
|
|
135
|
+
* which correctly handles the **untagged** CBOR payload that
|
|
136
|
+
* `mark.ur()` produces — symmetric with the constructor.
|
|
115
137
|
*/
|
|
116
138
|
static fromJSON(json: Record<string, unknown>): ProvenanceMarkInfo {
|
|
117
139
|
const urString = json["ur"] as string;
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const mark = ProvenanceMark.fromCborData(cborBytes);
|
|
140
|
+
const mark = ProvenanceMark.fromURString(urString);
|
|
141
|
+
const ur = mark.ur();
|
|
121
142
|
const bytewords = json["bytewords"] as string;
|
|
122
143
|
const bytemoji = json["bytemoji"] as string;
|
|
123
144
|
const comment = typeof json["comment"] === "string" ? json["comment"] : "";
|