@bcts/provenance-mark 1.0.0-alpha.10
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 +48 -0
- package/README.md +15 -0
- package/dist/index.cjs +1703 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +711 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +711 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +1700 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +1657 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
- package/src/crypto-utils.ts +75 -0
- package/src/date.ts +216 -0
- package/src/error.ts +141 -0
- package/src/generator.ts +182 -0
- package/src/index.ts +90 -0
- package/src/mark-info.ts +126 -0
- package/src/mark.ts +597 -0
- package/src/resolution.ts +294 -0
- package/src/rng-state.ts +68 -0
- package/src/seed.ts +98 -0
- package/src/utils.ts +103 -0
- package/src/validate.ts +449 -0
- package/src/xoshiro256starstar.ts +150 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/resolution.rs
|
|
2
|
+
|
|
3
|
+
import { type Cbor, cbor, expectUnsigned } from "@bcts/dcbor";
|
|
4
|
+
|
|
5
|
+
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
6
|
+
import {
|
|
7
|
+
serialize2Bytes,
|
|
8
|
+
deserialize2Bytes,
|
|
9
|
+
serialize4Bytes,
|
|
10
|
+
deserialize4Bytes,
|
|
11
|
+
serialize6Bytes,
|
|
12
|
+
deserialize6Bytes,
|
|
13
|
+
} from "./date.js";
|
|
14
|
+
|
|
15
|
+
// LOW (16 bytes)
|
|
16
|
+
// 0000 0000 0000 00 00
|
|
17
|
+
// 0123 4567 89ab cd ef
|
|
18
|
+
// key hash id seq date
|
|
19
|
+
|
|
20
|
+
// MEDIUM (32 bytes)
|
|
21
|
+
// 00000000 00000000 11111111 1111 1111
|
|
22
|
+
// 01234567 89abcdef 01234567 89ab cdef
|
|
23
|
+
// key hash id seq date
|
|
24
|
+
|
|
25
|
+
// QUARTILE (58 bytes)
|
|
26
|
+
// 0000000000000000 1111111111111111 2222222222222222 3333 333333
|
|
27
|
+
// 0123456789abcdef 0123456789abcdef 0123456789abcdef 0123 456789
|
|
28
|
+
// key hash id seq date
|
|
29
|
+
|
|
30
|
+
// HIGH (106 bytes)
|
|
31
|
+
// 00000000000000001111111111111111 22222222222222223333333333333333
|
|
32
|
+
// 44444444444444445555555555555555 6666 666666
|
|
33
|
+
// 0123456789abcdef0123456789abcdef 0123456789abcdef0123456789abcdef
|
|
34
|
+
// 0123456789abcdef0123456789abcdef 0123 456789 key
|
|
35
|
+
// hash id seq
|
|
36
|
+
// date
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolution levels for provenance marks.
|
|
40
|
+
* Higher resolution provides more security but larger mark sizes.
|
|
41
|
+
*/
|
|
42
|
+
export enum ProvenanceMarkResolution {
|
|
43
|
+
Low = 0,
|
|
44
|
+
Medium = 1,
|
|
45
|
+
Quartile = 2,
|
|
46
|
+
High = 3,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert a resolution to its numeric value.
|
|
51
|
+
*/
|
|
52
|
+
export function resolutionToNumber(res: ProvenanceMarkResolution): number {
|
|
53
|
+
return res as number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a resolution from a numeric value.
|
|
58
|
+
*/
|
|
59
|
+
export function resolutionFromNumber(value: number): ProvenanceMarkResolution {
|
|
60
|
+
switch (value) {
|
|
61
|
+
case 0:
|
|
62
|
+
return ProvenanceMarkResolution.Low;
|
|
63
|
+
case 1:
|
|
64
|
+
return ProvenanceMarkResolution.Medium;
|
|
65
|
+
case 2:
|
|
66
|
+
return ProvenanceMarkResolution.Quartile;
|
|
67
|
+
case 3:
|
|
68
|
+
return ProvenanceMarkResolution.High;
|
|
69
|
+
default:
|
|
70
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
71
|
+
details: `invalid provenance mark resolution value: ${value}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the link length (key/hash/chainID length) for a resolution.
|
|
78
|
+
*/
|
|
79
|
+
export function linkLength(res: ProvenanceMarkResolution): number {
|
|
80
|
+
switch (res) {
|
|
81
|
+
case ProvenanceMarkResolution.Low:
|
|
82
|
+
return 4;
|
|
83
|
+
case ProvenanceMarkResolution.Medium:
|
|
84
|
+
return 8;
|
|
85
|
+
case ProvenanceMarkResolution.Quartile:
|
|
86
|
+
return 16;
|
|
87
|
+
case ProvenanceMarkResolution.High:
|
|
88
|
+
return 32;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the sequence bytes length for a resolution.
|
|
94
|
+
*/
|
|
95
|
+
export function seqBytesLength(res: ProvenanceMarkResolution): number {
|
|
96
|
+
switch (res) {
|
|
97
|
+
case ProvenanceMarkResolution.Low:
|
|
98
|
+
return 2;
|
|
99
|
+
case ProvenanceMarkResolution.Medium:
|
|
100
|
+
case ProvenanceMarkResolution.Quartile:
|
|
101
|
+
case ProvenanceMarkResolution.High:
|
|
102
|
+
return 4;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the date bytes length for a resolution.
|
|
108
|
+
*/
|
|
109
|
+
export function dateBytesLength(res: ProvenanceMarkResolution): number {
|
|
110
|
+
switch (res) {
|
|
111
|
+
case ProvenanceMarkResolution.Low:
|
|
112
|
+
return 2;
|
|
113
|
+
case ProvenanceMarkResolution.Medium:
|
|
114
|
+
return 4;
|
|
115
|
+
case ProvenanceMarkResolution.Quartile:
|
|
116
|
+
case ProvenanceMarkResolution.High:
|
|
117
|
+
return 6;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the fixed-length portion size for a resolution.
|
|
123
|
+
*/
|
|
124
|
+
export function fixedLength(res: ProvenanceMarkResolution): number {
|
|
125
|
+
return linkLength(res) * 3 + seqBytesLength(res) + dateBytesLength(res);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the key range for a resolution.
|
|
130
|
+
*/
|
|
131
|
+
export function keyRange(res: ProvenanceMarkResolution): { start: number; end: number } {
|
|
132
|
+
return { start: 0, end: linkLength(res) };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get the chain ID range for a resolution.
|
|
137
|
+
*/
|
|
138
|
+
export function chainIdRange(res: ProvenanceMarkResolution): { start: number; end: number } {
|
|
139
|
+
return { start: 0, end: linkLength(res) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the hash range for a resolution.
|
|
144
|
+
*/
|
|
145
|
+
export function hashRange(res: ProvenanceMarkResolution): { start: number; end: number } {
|
|
146
|
+
const chainIdEnd = chainIdRange(res).end;
|
|
147
|
+
return { start: chainIdEnd, end: chainIdEnd + linkLength(res) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get the sequence bytes range for a resolution.
|
|
152
|
+
*/
|
|
153
|
+
export function seqBytesRange(res: ProvenanceMarkResolution): { start: number; end: number } {
|
|
154
|
+
const hashEnd = hashRange(res).end;
|
|
155
|
+
return { start: hashEnd, end: hashEnd + seqBytesLength(res) };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the date bytes range for a resolution.
|
|
160
|
+
*/
|
|
161
|
+
export function dateBytesRange(res: ProvenanceMarkResolution): { start: number; end: number } {
|
|
162
|
+
const seqEnd = seqBytesRange(res).end;
|
|
163
|
+
return { start: seqEnd, end: seqEnd + dateBytesLength(res) };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get the info range start for a resolution.
|
|
168
|
+
*/
|
|
169
|
+
export function infoRangeStart(res: ProvenanceMarkResolution): number {
|
|
170
|
+
return dateBytesRange(res).end;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Serialize a Date into bytes based on the resolution.
|
|
175
|
+
*/
|
|
176
|
+
export function serializeDate(res: ProvenanceMarkResolution, date: Date): Uint8Array {
|
|
177
|
+
switch (res) {
|
|
178
|
+
case ProvenanceMarkResolution.Low:
|
|
179
|
+
return serialize2Bytes(date);
|
|
180
|
+
case ProvenanceMarkResolution.Medium:
|
|
181
|
+
return serialize4Bytes(date);
|
|
182
|
+
case ProvenanceMarkResolution.Quartile:
|
|
183
|
+
case ProvenanceMarkResolution.High:
|
|
184
|
+
return serialize6Bytes(date);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Deserialize bytes into a Date based on the resolution.
|
|
190
|
+
*/
|
|
191
|
+
export function deserializeDate(res: ProvenanceMarkResolution, data: Uint8Array): Date {
|
|
192
|
+
switch (res) {
|
|
193
|
+
case ProvenanceMarkResolution.Low:
|
|
194
|
+
if (data.length !== 2) {
|
|
195
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
196
|
+
details: `invalid date length: expected 2 bytes, got ${data.length}`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return deserialize2Bytes(data);
|
|
200
|
+
case ProvenanceMarkResolution.Medium:
|
|
201
|
+
if (data.length !== 4) {
|
|
202
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
203
|
+
details: `invalid date length: expected 4 bytes, got ${data.length}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return deserialize4Bytes(data);
|
|
207
|
+
case ProvenanceMarkResolution.Quartile:
|
|
208
|
+
case ProvenanceMarkResolution.High:
|
|
209
|
+
if (data.length !== 6) {
|
|
210
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
211
|
+
details: `invalid date length: expected 6 bytes, got ${data.length}`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return deserialize6Bytes(data);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Serialize a sequence number into bytes based on the resolution.
|
|
220
|
+
*/
|
|
221
|
+
export function serializeSeq(res: ProvenanceMarkResolution, seq: number): Uint8Array {
|
|
222
|
+
const len = seqBytesLength(res);
|
|
223
|
+
if (len === 2) {
|
|
224
|
+
if (seq > 0xffff) {
|
|
225
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
226
|
+
details: `sequence number ${seq} out of range for 2-byte format (max ${0xffff})`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const buf = new Uint8Array(2);
|
|
230
|
+
buf[0] = (seq >> 8) & 0xff;
|
|
231
|
+
buf[1] = seq & 0xff;
|
|
232
|
+
return buf;
|
|
233
|
+
} else {
|
|
234
|
+
const buf = new Uint8Array(4);
|
|
235
|
+
buf[0] = (seq >> 24) & 0xff;
|
|
236
|
+
buf[1] = (seq >> 16) & 0xff;
|
|
237
|
+
buf[2] = (seq >> 8) & 0xff;
|
|
238
|
+
buf[3] = seq & 0xff;
|
|
239
|
+
return buf;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Deserialize bytes into a sequence number based on the resolution.
|
|
245
|
+
*/
|
|
246
|
+
export function deserializeSeq(res: ProvenanceMarkResolution, data: Uint8Array): number {
|
|
247
|
+
const len = seqBytesLength(res);
|
|
248
|
+
if (len === 2) {
|
|
249
|
+
if (data.length !== 2) {
|
|
250
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
251
|
+
details: `invalid sequence number length: expected 2 bytes, got ${data.length}`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return (data[0] << 8) | data[1];
|
|
255
|
+
} else {
|
|
256
|
+
if (data.length !== 4) {
|
|
257
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.ResolutionError, undefined, {
|
|
258
|
+
details: `invalid sequence number length: expected 4 bytes, got ${data.length}`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) >>> 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the string representation of a resolution.
|
|
267
|
+
*/
|
|
268
|
+
export function resolutionToString(res: ProvenanceMarkResolution): string {
|
|
269
|
+
switch (res) {
|
|
270
|
+
case ProvenanceMarkResolution.Low:
|
|
271
|
+
return "low";
|
|
272
|
+
case ProvenanceMarkResolution.Medium:
|
|
273
|
+
return "medium";
|
|
274
|
+
case ProvenanceMarkResolution.Quartile:
|
|
275
|
+
return "quartile";
|
|
276
|
+
case ProvenanceMarkResolution.High:
|
|
277
|
+
return "high";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Convert a resolution to CBOR.
|
|
283
|
+
*/
|
|
284
|
+
export function resolutionToCbor(res: ProvenanceMarkResolution): Cbor {
|
|
285
|
+
return cbor(res as number);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Create a resolution from CBOR.
|
|
290
|
+
*/
|
|
291
|
+
export function resolutionFromCbor(cborValue: Cbor): ProvenanceMarkResolution {
|
|
292
|
+
const value = expectUnsigned(cborValue);
|
|
293
|
+
return resolutionFromNumber(Number(value));
|
|
294
|
+
}
|
package/src/rng-state.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/rng_state.rs
|
|
2
|
+
|
|
3
|
+
import { type Cbor, cbor, expectBytes } from "@bcts/dcbor";
|
|
4
|
+
|
|
5
|
+
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
6
|
+
|
|
7
|
+
export const RNG_STATE_LENGTH = 32;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* RNG state for provenance marks (32 bytes).
|
|
11
|
+
*/
|
|
12
|
+
export class RngState {
|
|
13
|
+
private readonly data: Uint8Array;
|
|
14
|
+
|
|
15
|
+
private constructor(data: Uint8Array) {
|
|
16
|
+
this.data = data;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the raw bytes.
|
|
21
|
+
*/
|
|
22
|
+
toBytes(): Uint8Array {
|
|
23
|
+
return new Uint8Array(this.data);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create from a 32-byte array.
|
|
28
|
+
*/
|
|
29
|
+
static fromBytes(bytes: Uint8Array): RngState {
|
|
30
|
+
if (bytes.length !== RNG_STATE_LENGTH) {
|
|
31
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.InvalidSeedLength, undefined, {
|
|
32
|
+
actual: bytes.length,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return new RngState(new Uint8Array(bytes));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create from a slice (validates length).
|
|
40
|
+
*/
|
|
41
|
+
static fromSlice(bytes: Uint8Array): RngState {
|
|
42
|
+
return RngState.fromBytes(bytes);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the hex representation.
|
|
47
|
+
*/
|
|
48
|
+
hex(): string {
|
|
49
|
+
return Array.from(this.data)
|
|
50
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
51
|
+
.join("");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert to CBOR (byte string).
|
|
56
|
+
*/
|
|
57
|
+
toCbor(): Cbor {
|
|
58
|
+
return cbor(this.data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create from CBOR (byte string).
|
|
63
|
+
*/
|
|
64
|
+
static fromCbor(cborValue: Cbor): RngState {
|
|
65
|
+
const bytes = expectBytes(cborValue);
|
|
66
|
+
return RngState.fromBytes(bytes);
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/seed.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/seed.rs
|
|
2
|
+
|
|
3
|
+
import { type Cbor, cbor, expectBytes } from "@bcts/dcbor";
|
|
4
|
+
import { randomData } from "@bcts/rand";
|
|
5
|
+
|
|
6
|
+
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
7
|
+
import { extendKey } from "./crypto-utils.js";
|
|
8
|
+
|
|
9
|
+
export const PROVENANCE_SEED_LENGTH = 32;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A seed for generating provenance marks.
|
|
13
|
+
*/
|
|
14
|
+
export class ProvenanceSeed {
|
|
15
|
+
private readonly data: Uint8Array;
|
|
16
|
+
|
|
17
|
+
private constructor(data: Uint8Array) {
|
|
18
|
+
this.data = data;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new random seed using secure random number generation.
|
|
23
|
+
*/
|
|
24
|
+
static new(): ProvenanceSeed {
|
|
25
|
+
const data = randomData(PROVENANCE_SEED_LENGTH);
|
|
26
|
+
return ProvenanceSeed.fromBytes(data);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a new seed using custom random data.
|
|
31
|
+
*/
|
|
32
|
+
static newUsing(randomData: Uint8Array): ProvenanceSeed {
|
|
33
|
+
if (randomData.length < PROVENANCE_SEED_LENGTH) {
|
|
34
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.InvalidSeedLength, undefined, {
|
|
35
|
+
actual: randomData.length,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return ProvenanceSeed.fromBytes(randomData.slice(0, PROVENANCE_SEED_LENGTH));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a new seed from a passphrase.
|
|
43
|
+
*/
|
|
44
|
+
static newWithPassphrase(passphrase: string): ProvenanceSeed {
|
|
45
|
+
const seedData = extendKey(new TextEncoder().encode(passphrase));
|
|
46
|
+
return ProvenanceSeed.fromBytes(seedData);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the raw bytes.
|
|
51
|
+
*/
|
|
52
|
+
toBytes(): Uint8Array {
|
|
53
|
+
return new Uint8Array(this.data);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create from a 32-byte array.
|
|
58
|
+
*/
|
|
59
|
+
static fromBytes(bytes: Uint8Array): ProvenanceSeed {
|
|
60
|
+
if (bytes.length !== PROVENANCE_SEED_LENGTH) {
|
|
61
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.InvalidSeedLength, undefined, {
|
|
62
|
+
actual: bytes.length,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return new ProvenanceSeed(new Uint8Array(bytes));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create from a slice (validates length).
|
|
70
|
+
*/
|
|
71
|
+
static fromSlice(bytes: Uint8Array): ProvenanceSeed {
|
|
72
|
+
return ProvenanceSeed.fromBytes(bytes);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the hex representation.
|
|
77
|
+
*/
|
|
78
|
+
hex(): string {
|
|
79
|
+
return Array.from(this.data)
|
|
80
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
81
|
+
.join("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Convert to CBOR (byte string).
|
|
86
|
+
*/
|
|
87
|
+
toCbor(): Cbor {
|
|
88
|
+
return cbor(this.data);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create from CBOR (byte string).
|
|
93
|
+
*/
|
|
94
|
+
static fromCbor(cborValue: Cbor): ProvenanceSeed {
|
|
95
|
+
const bytes = expectBytes(cborValue);
|
|
96
|
+
return ProvenanceSeed.fromBytes(bytes);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for byte array conversions.
|
|
3
|
+
*
|
|
4
|
+
* These functions provide cross-platform support for common byte manipulation
|
|
5
|
+
* operations needed in provenance mark encoding.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Declare Node.js types for environments where they might not be available
|
|
9
|
+
declare const require: ((module: string) => unknown) | undefined;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert a Uint8Array to a lowercase hexadecimal string.
|
|
13
|
+
*
|
|
14
|
+
* @param data - The byte array to convert
|
|
15
|
+
* @returns A lowercase hex string representation (2 characters per byte)
|
|
16
|
+
*/
|
|
17
|
+
export function bytesToHex(data: Uint8Array): string {
|
|
18
|
+
return Array.from(data)
|
|
19
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert a hexadecimal string to a Uint8Array.
|
|
25
|
+
*
|
|
26
|
+
* @param hex - A hex string (must have even length, case-insensitive)
|
|
27
|
+
* @returns The decoded byte array
|
|
28
|
+
* @throws {Error} If the hex string has odd length or contains invalid characters
|
|
29
|
+
*/
|
|
30
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
31
|
+
if (hex.length % 2 !== 0) {
|
|
32
|
+
throw new Error(`Hex string must have even length, got ${hex.length}`);
|
|
33
|
+
}
|
|
34
|
+
if (!/^[0-9A-Fa-f]*$/.test(hex)) {
|
|
35
|
+
throw new Error("Invalid hex string: contains non-hexadecimal characters");
|
|
36
|
+
}
|
|
37
|
+
const data = new Uint8Array(hex.length / 2);
|
|
38
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
39
|
+
data[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert a Uint8Array to a base64-encoded string.
|
|
46
|
+
*
|
|
47
|
+
* This function works in both browser and Node.js environments.
|
|
48
|
+
*
|
|
49
|
+
* @param data - The byte array to encode
|
|
50
|
+
* @returns A base64-encoded string
|
|
51
|
+
*/
|
|
52
|
+
export function toBase64(data: Uint8Array): string {
|
|
53
|
+
// Use globalThis.btoa for browser/modern Node.js compatibility
|
|
54
|
+
const globalBtoa = globalThis.btoa as ((data: string) => string) | undefined;
|
|
55
|
+
if (typeof globalBtoa === "function") {
|
|
56
|
+
// Convert bytes to binary string without spread operator to avoid
|
|
57
|
+
// call stack limits for large arrays
|
|
58
|
+
let binary = "";
|
|
59
|
+
for (const byte of data) {
|
|
60
|
+
binary += String.fromCharCode(byte);
|
|
61
|
+
}
|
|
62
|
+
return globalBtoa(binary);
|
|
63
|
+
}
|
|
64
|
+
// Node.js environment (fallback for Node < 18)
|
|
65
|
+
const requireFn = require;
|
|
66
|
+
if (typeof requireFn === "function") {
|
|
67
|
+
const { Buffer: NodeBuffer } = requireFn("buffer") as {
|
|
68
|
+
Buffer: { from: (data: Uint8Array) => { toString: (encoding: string) => string } };
|
|
69
|
+
};
|
|
70
|
+
return NodeBuffer.from(data).toString("base64");
|
|
71
|
+
}
|
|
72
|
+
throw new Error("btoa not available and require is not defined");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert a base64-encoded string to a Uint8Array.
|
|
77
|
+
*
|
|
78
|
+
* This function works in both browser and Node.js environments.
|
|
79
|
+
*
|
|
80
|
+
* @param base64 - A base64-encoded string
|
|
81
|
+
* @returns The decoded byte array
|
|
82
|
+
*/
|
|
83
|
+
export function fromBase64(base64: string): Uint8Array {
|
|
84
|
+
// Use globalThis.atob for browser/modern Node.js compatibility
|
|
85
|
+
const globalAtob = globalThis.atob as ((data: string) => string) | undefined;
|
|
86
|
+
if (typeof globalAtob === "function") {
|
|
87
|
+
const binary = globalAtob(base64);
|
|
88
|
+
const bytes = new Uint8Array(binary.length);
|
|
89
|
+
for (let i = 0; i < binary.length; i++) {
|
|
90
|
+
bytes[i] = binary.charCodeAt(i);
|
|
91
|
+
}
|
|
92
|
+
return bytes;
|
|
93
|
+
}
|
|
94
|
+
// Node.js environment (fallback for Node < 18)
|
|
95
|
+
const requireFn = require;
|
|
96
|
+
if (typeof requireFn === "function") {
|
|
97
|
+
const { Buffer: NodeBuffer } = requireFn("buffer") as {
|
|
98
|
+
Buffer: { from: (data: string, encoding: string) => Uint8Array };
|
|
99
|
+
};
|
|
100
|
+
return new Uint8Array(NodeBuffer.from(base64, "base64"));
|
|
101
|
+
}
|
|
102
|
+
throw new Error("atob not available and require is not defined");
|
|
103
|
+
}
|