@bcts/envelope 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.
Files changed (44) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +23 -0
  3. package/dist/index.cjs +2646 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +782 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +782 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +2644 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +2552 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +84 -0
  14. package/src/base/assertion.ts +179 -0
  15. package/src/base/assertions.ts +153 -0
  16. package/src/base/cbor.ts +122 -0
  17. package/src/base/digest.ts +204 -0
  18. package/src/base/elide.ts +390 -0
  19. package/src/base/envelope-decodable.ts +186 -0
  20. package/src/base/envelope-encodable.ts +71 -0
  21. package/src/base/envelope.ts +988 -0
  22. package/src/base/error.ts +421 -0
  23. package/src/base/index.ts +56 -0
  24. package/src/base/leaf.ts +147 -0
  25. package/src/base/queries.ts +244 -0
  26. package/src/base/walk.ts +215 -0
  27. package/src/base/wrap.ts +26 -0
  28. package/src/extension/attachment.ts +280 -0
  29. package/src/extension/compress.ts +176 -0
  30. package/src/extension/encrypt.ts +297 -0
  31. package/src/extension/expression.ts +404 -0
  32. package/src/extension/index.ts +72 -0
  33. package/src/extension/proof.ts +227 -0
  34. package/src/extension/recipient.ts +440 -0
  35. package/src/extension/salt.ts +114 -0
  36. package/src/extension/signature.ts +398 -0
  37. package/src/extension/types.ts +92 -0
  38. package/src/format/diagnostic.ts +116 -0
  39. package/src/format/hex.ts +25 -0
  40. package/src/format/index.ts +13 -0
  41. package/src/format/tree.ts +168 -0
  42. package/src/index.ts +32 -0
  43. package/src/utils/index.ts +8 -0
  44. package/src/utils/string.ts +48 -0
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@bcts/envelope",
3
+ "version": "1.0.0-alpha.10",
4
+ "type": "module",
5
+ "description": "Gordian Envelope for TypeScript",
6
+ "license": "BSD-2-Clause-Patent",
7
+ "author": "Leonardo Custodio <leonardo@custodio.me>",
8
+ "homepage": "https://bcts.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/leonardocustodio/bcts",
12
+ "directory": "packages/envelope"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/leonardocustodio/bcts/issues"
16
+ },
17
+ "main": "dist/index.cjs",
18
+ "module": "dist/index.mjs",
19
+ "types": "dist/index.d.mts",
20
+ "browser": "dist/index.iife.js",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.mts",
24
+ "import": "./dist/index.mjs",
25
+ "require": "./dist/index.cjs",
26
+ "default": "./dist/index.mjs"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "src",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsdown",
36
+ "dev": "tsdown --watch",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
40
+ "lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
41
+ "typecheck": "tsc --noEmit",
42
+ "clean": "rm -rf dist",
43
+ "docs": "typedoc",
44
+ "prepublishOnly": "npm run clean && npm run build"
45
+ },
46
+ "keywords": [
47
+ "envelope",
48
+ "cbor",
49
+ "dcbor",
50
+ "gordian",
51
+ "blockchain-commons",
52
+ "privacy",
53
+ "encryption",
54
+ "deterministic",
55
+ "encoding",
56
+ "serialization"
57
+ ],
58
+ "engines": {
59
+ "node": ">=18.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@bcts/eslint": "^0.1.0",
63
+ "@bcts/tsconfig": "^0.1.0",
64
+ "@eslint/js": "^9.39.2",
65
+ "@types/node": "^25.0.3",
66
+ "@types/pako": "^2.0.3",
67
+ "eslint": "^9.39.2",
68
+ "prettier": "^3.2.5",
69
+ "ts-node": "^10.9.2",
70
+ "tsdown": "^0.18.3",
71
+ "typedoc": "^0.28.15",
72
+ "typescript": "^5.9.3",
73
+ "vitest": "^4.0.16"
74
+ },
75
+ "dependencies": {
76
+ "@bcts/components": "^1.0.0-alpha.10",
77
+ "@bcts/crypto": "^1.0.0-alpha.10",
78
+ "@bcts/dcbor": "^1.0.0-alpha.10",
79
+ "@bcts/known-values": "^1.0.0-alpha.10",
80
+ "@bcts/rand": "^1.0.0-alpha.10",
81
+ "@bcts/uniform-resources": "^1.0.0-alpha.10",
82
+ "pako": "^2.1.0"
83
+ }
84
+ }
@@ -0,0 +1,179 @@
1
+ import { Digest, type DigestProvider } from "./digest";
2
+ import { Envelope } from "./envelope";
3
+ import { type EnvelopeEncodable } from "./envelope-encodable";
4
+ import { EnvelopeError } from "./error";
5
+ import type { Cbor } from "@bcts/dcbor";
6
+ import { CborMap } from "@bcts/dcbor";
7
+
8
+ /// A predicate-object relationship representing an assertion about a subject.
9
+ ///
10
+ /// In Gordian Envelope, assertions are the basic building blocks for attaching
11
+ /// information to a subject. An assertion consists of a predicate (which states
12
+ /// what is being asserted) and an object (which provides the assertion's
13
+ /// value).
14
+ ///
15
+ /// Assertions can be attached to envelope subjects to form semantic statements
16
+ /// like: "subject hasAttribute value" or "document signedBy signature".
17
+ ///
18
+ /// Assertions are equivalent to RDF (Resource Description Framework) triples,
19
+ /// where:
20
+ /// - The envelope's subject is the subject of the triple
21
+ /// - The assertion's predicate is the predicate of the triple
22
+ /// - The assertion's object is the object of the triple
23
+ ///
24
+ /// Generally you do not create an instance of this type directly, but
25
+ /// instead use `Envelope.newAssertion()`, or the various functions
26
+ /// on `Envelope` that create assertions.
27
+ export class Assertion implements DigestProvider {
28
+ readonly #predicate: Envelope;
29
+ readonly #object: Envelope;
30
+ readonly #digest: Digest;
31
+
32
+ /// Creates a new assertion and calculates its digest.
33
+ ///
34
+ /// This constructor takes a predicate and object, both of which are
35
+ /// converted to envelopes using the `EnvelopeEncodable` trait. It then
36
+ /// calculates the assertion's digest by combining the digests of the
37
+ /// predicate and object.
38
+ ///
39
+ /// The digest is calculated according to the Gordian Envelope
40
+ /// specification, which ensures that semantically equivalent assertions
41
+ /// always produce the same digest.
42
+ ///
43
+ /// @param predicate - The predicate of the assertion, which states what is
44
+ /// being asserted
45
+ /// @param object - The object of the assertion, which provides the assertion's
46
+ /// value
47
+ ///
48
+ /// @returns A new assertion with the specified predicate, object, and calculated
49
+ /// digest.
50
+ ///
51
+ /// @example
52
+ /// ```typescript
53
+ /// // Direct method - create an assertion envelope
54
+ /// const assertionEnvelope = Envelope.newAssertion("name", "Alice");
55
+ ///
56
+ /// // Or create and add an assertion to a subject
57
+ /// const person = Envelope.new("person").addAssertion("name", "Alice");
58
+ /// ```
59
+ constructor(predicate: EnvelopeEncodable | Envelope, object: EnvelopeEncodable | Envelope) {
60
+ this.#predicate = predicate instanceof Envelope ? predicate : Envelope.new(predicate);
61
+ this.#object = object instanceof Envelope ? object : Envelope.new(object);
62
+ this.#digest = Digest.fromDigests([this.#predicate.digest(), this.#object.digest()]);
63
+ }
64
+
65
+ /// Returns the predicate of the assertion.
66
+ ///
67
+ /// The predicate states what is being asserted about the subject. It is
68
+ /// typically a string or known value, but can be any envelope.
69
+ ///
70
+ /// @returns A clone of the assertion's predicate envelope.
71
+ predicate(): Envelope {
72
+ return this.#predicate;
73
+ }
74
+
75
+ /// Returns the object of the assertion.
76
+ ///
77
+ /// The object provides the value or content of the assertion. It can be any
78
+ /// type that can be represented as an envelope.
79
+ ///
80
+ /// @returns A clone of the assertion's object envelope.
81
+ object(): Envelope {
82
+ return this.#object;
83
+ }
84
+
85
+ /// Returns the digest of this assertion.
86
+ ///
87
+ /// Implementation of the DigestProvider interface.
88
+ ///
89
+ /// @returns The assertion's digest
90
+ digest(): Digest {
91
+ return this.#digest;
92
+ }
93
+
94
+ /// Checks if two assertions are equal based on digest equality.
95
+ ///
96
+ /// Two assertions are considered equal if they have the same digest,
97
+ /// regardless of how they were constructed.
98
+ ///
99
+ /// @param other - The other assertion to compare with
100
+ /// @returns `true` if the assertions are equal, `false` otherwise
101
+ equals(other: Assertion): boolean {
102
+ return this.#digest.equals(other.#digest);
103
+ }
104
+
105
+ /// Converts this assertion to CBOR.
106
+ ///
107
+ /// The CBOR representation of an assertion is a map with a single key-value
108
+ /// pair, where the key is the predicate's CBOR and the value is the object's
109
+ /// CBOR.
110
+ ///
111
+ /// @returns A CBOR representation of this assertion
112
+ toCbor(): Cbor {
113
+ const map = new CborMap();
114
+ map.set(this.#predicate.untaggedCbor(), this.#object.untaggedCbor());
115
+ return map as unknown as Cbor;
116
+ }
117
+
118
+ /// Attempts to create an assertion from a CBOR value.
119
+ ///
120
+ /// The CBOR must be a map with exactly one entry, where the key represents
121
+ /// the predicate and the value represents the object.
122
+ ///
123
+ /// @param cbor - The CBOR value to convert
124
+ /// @returns A new Assertion instance
125
+ /// @throws {EnvelopeError} If the CBOR is not a valid assertion
126
+ static fromCbor(cbor: Cbor): Assertion {
127
+ // Check if cbor is a Map
128
+ if (!(cbor instanceof CborMap)) {
129
+ throw EnvelopeError.invalidAssertion();
130
+ }
131
+
132
+ return Assertion.fromCborMap(cbor);
133
+ }
134
+
135
+ /// Attempts to create an assertion from a CBOR map.
136
+ ///
137
+ /// The map must have exactly one entry, where the key represents the
138
+ /// predicate and the value represents the object. This is used in
139
+ /// the deserialization process.
140
+ ///
141
+ /// @param map - The CBOR map to convert
142
+ /// @returns A new Assertion instance
143
+ /// @throws {EnvelopeError} If the map doesn't have exactly one entry
144
+ static fromCborMap(map: CborMap): Assertion {
145
+ if (map.size !== 1) {
146
+ throw EnvelopeError.invalidAssertion();
147
+ }
148
+
149
+ const entries = Array.from(map.entries());
150
+ const firstEntry = entries[0];
151
+ if (firstEntry === undefined) {
152
+ throw EnvelopeError.invalidAssertion();
153
+ }
154
+ const [predicateCbor, objectCbor] = firstEntry;
155
+
156
+ const predicate = Envelope.fromUntaggedCbor(predicateCbor);
157
+
158
+ const object = Envelope.fromUntaggedCbor(objectCbor);
159
+
160
+ return new Assertion(predicate, object);
161
+ }
162
+
163
+ /// Creates a string representation of this assertion for debugging.
164
+ ///
165
+ /// @returns A string representation
166
+ toString(): string {
167
+ return `Assertion(${String(this.#predicate)}: ${String(this.#object)})`;
168
+ }
169
+
170
+ /// Creates a copy of this assertion.
171
+ ///
172
+ /// Since assertions are immutable and envelopes are cheap to clone,
173
+ /// this returns the same instance.
174
+ ///
175
+ /// @returns This assertion instance
176
+ clone(): Assertion {
177
+ return this;
178
+ }
179
+ }
@@ -0,0 +1,153 @@
1
+ import { Envelope } from "./envelope";
2
+ import type { EnvelopeEncodableValue } from "./envelope-encodable";
3
+ import { EnvelopeError } from "./error";
4
+
5
+ /// Support for adding assertions.
6
+ ///
7
+ /// Assertions are predicate-object pairs that make statements about an
8
+ /// envelope's subject. This implementation provides methods for adding various
9
+ /// types of assertions to envelopes.
10
+ ///
11
+ /// These methods extend the Envelope class to provide a rich API for
12
+ /// working with assertions, matching the Rust bc-envelope implementation.
13
+
14
+ /// Implementation of addAssertionEnvelopes
15
+ Envelope.prototype.addAssertionEnvelopes = function (
16
+ this: Envelope,
17
+ assertions: Envelope[],
18
+ ): Envelope {
19
+ return assertions.reduce((result, assertion) => result.addAssertionEnvelope(assertion), this);
20
+ };
21
+
22
+ /// Implementation of addOptionalAssertionEnvelope
23
+ Envelope.prototype.addOptionalAssertionEnvelope = function (
24
+ this: Envelope,
25
+ assertion: Envelope | undefined,
26
+ ): Envelope {
27
+ if (assertion === undefined) {
28
+ return this;
29
+ }
30
+
31
+ // Validate that the assertion is a valid assertion or obscured envelope
32
+ if (!assertion.isSubjectAssertion() && !assertion.isSubjectObscured()) {
33
+ throw EnvelopeError.invalidFormat();
34
+ }
35
+
36
+ const c = this.case();
37
+
38
+ // Check if this is already a node
39
+ if (c.type === "node") {
40
+ // Check for duplicate assertions
41
+ const isDuplicate = c.assertions.some((a) => a.digest().equals(assertion.digest()));
42
+ if (isDuplicate) {
43
+ return this;
44
+ }
45
+
46
+ // Add the new assertion
47
+ return Envelope.newWithUncheckedAssertions(c.subject, [...c.assertions, assertion]);
48
+ }
49
+
50
+ // Otherwise, create a new node with this envelope as subject
51
+ return Envelope.newWithUncheckedAssertions(this.subject(), [assertion]);
52
+ };
53
+
54
+ /// Implementation of addOptionalAssertion
55
+ Envelope.prototype.addOptionalAssertion = function (
56
+ this: Envelope,
57
+ predicate: EnvelopeEncodableValue,
58
+ object: EnvelopeEncodableValue | undefined,
59
+ ): Envelope {
60
+ if (object === undefined || object === null) {
61
+ return this;
62
+ }
63
+ return this.addAssertion(predicate, object);
64
+ };
65
+
66
+ /// Implementation of addNonemptyStringAssertion
67
+ Envelope.prototype.addNonemptyStringAssertion = function (
68
+ this: Envelope,
69
+ predicate: EnvelopeEncodableValue,
70
+ str: string,
71
+ ): Envelope {
72
+ if (str.length === 0) {
73
+ return this;
74
+ }
75
+ return this.addAssertion(predicate, str);
76
+ };
77
+
78
+ /// Implementation of addAssertions
79
+ Envelope.prototype.addAssertions = function (this: Envelope, envelopes: Envelope[]): Envelope {
80
+ return envelopes.reduce((result, envelope) => result.addAssertionEnvelope(envelope), this);
81
+ };
82
+
83
+ /// Implementation of addAssertionIf
84
+ Envelope.prototype.addAssertionIf = function (
85
+ this: Envelope,
86
+ condition: boolean,
87
+ predicate: EnvelopeEncodableValue,
88
+ object: EnvelopeEncodableValue,
89
+ ): Envelope {
90
+ if (condition) {
91
+ return this.addAssertion(predicate, object);
92
+ }
93
+ return this;
94
+ };
95
+
96
+ /// Implementation of addAssertionEnvelopeIf
97
+ Envelope.prototype.addAssertionEnvelopeIf = function (
98
+ this: Envelope,
99
+ condition: boolean,
100
+ assertionEnvelope: Envelope,
101
+ ): Envelope {
102
+ if (condition) {
103
+ return this.addAssertionEnvelope(assertionEnvelope);
104
+ }
105
+ return this;
106
+ };
107
+
108
+ /// Implementation of removeAssertion
109
+ Envelope.prototype.removeAssertion = function (this: Envelope, target: Envelope): Envelope {
110
+ const assertions = this.assertions();
111
+ const targetDigest = target.digest();
112
+
113
+ const index = assertions.findIndex((a) => a.digest().equals(targetDigest));
114
+
115
+ if (index === -1) {
116
+ // Assertion not found, return unchanged
117
+ return this;
118
+ }
119
+
120
+ // Remove the assertion
121
+ const newAssertions = [...assertions.slice(0, index), ...assertions.slice(index + 1)];
122
+
123
+ if (newAssertions.length === 0) {
124
+ // No assertions left, return just the subject
125
+ return this.subject();
126
+ }
127
+
128
+ // Return envelope with remaining assertions
129
+ return Envelope.newWithUncheckedAssertions(this.subject(), newAssertions);
130
+ };
131
+
132
+ /// Implementation of replaceAssertion
133
+ Envelope.prototype.replaceAssertion = function (
134
+ this: Envelope,
135
+ assertion: Envelope,
136
+ newAssertion: Envelope,
137
+ ): Envelope {
138
+ return this.removeAssertion(assertion).addAssertionEnvelope(newAssertion);
139
+ };
140
+
141
+ /// Implementation of replaceSubject
142
+ Envelope.prototype.replaceSubject = function (this: Envelope, subject: Envelope): Envelope {
143
+ return this.assertions().reduce((e, a) => e.addAssertionEnvelope(a), subject);
144
+ };
145
+
146
+ /// Implementation of assertions
147
+ Envelope.prototype.assertions = function (this: Envelope): Envelope[] {
148
+ const c = this.case();
149
+ if (c.type === "node") {
150
+ return c.assertions;
151
+ }
152
+ return [];
153
+ };
@@ -0,0 +1,122 @@
1
+ import type { Cbor } from "@bcts/dcbor";
2
+ import {
3
+ type CborTagged,
4
+ type CborTaggedEncodable,
5
+ type CborTaggedDecodable,
6
+ tagsForValues,
7
+ cborData,
8
+ decodeCbor,
9
+ } from "@bcts/dcbor";
10
+ import { ENVELOPE } from "@bcts/components";
11
+ import { Envelope } from "./envelope";
12
+
13
+ const TAG_ENVELOPE = ENVELOPE.value;
14
+
15
+ /// Support for CBOR encoding and decoding of `Envelope`.
16
+ ///
17
+ /// All envelopes are tagged with the `envelope` tag (200). Within that tag,
18
+ /// each of the envelope cases has a unique CBOR signature:
19
+ ///
20
+ /// * `.node` contains a CBOR array, the first element of which is the subject,
21
+ /// followed by one or more assertions.
22
+ /// * `.leaf` is tagged #6.24 (TAG_ENCODED_CBOR) or #6.204 (TAG_LEAF), which
23
+ /// are the IANA tag for embedded CBOR.
24
+ /// * `.wrapped` is tagged with the `envelope` tag.
25
+ /// * `.assertion` is a single-element map `{predicate: object}`.
26
+ /// * `.knownValue` is an unsigned 64-bit integer.
27
+ /// * `.encrypted` is tagged with the `crypto-msg` tag.
28
+ /// * `.elided` is a byte string of length 32 (the digest).
29
+ /// * `.compressed` is tagged with the `compressed` tag.
30
+ ///
31
+ /// This module provides implementations of the CBOR encoding/decoding traits
32
+ /// for the Envelope type, matching the Rust bc-envelope implementation.
33
+
34
+ /// Implements CborTagged interface for Envelope.
35
+ ///
36
+ /// Returns the tags that should be used for CBOR encoding.
37
+ export class EnvelopeCBORTagged implements CborTagged {
38
+ cborTags(): ReturnType<typeof tagsForValues> {
39
+ return tagsForValues([TAG_ENVELOPE]);
40
+ }
41
+
42
+ static cborTags(): number[] {
43
+ return tagsForValues([TAG_ENVELOPE]).map((tag) => Number(tag.value));
44
+ }
45
+ }
46
+
47
+ /// Implements CborTaggedEncodable for Envelope.
48
+ ///
49
+ /// Provides the untagged CBOR representation of an envelope.
50
+ export class EnvelopeCBORTaggedEncodable implements CborTaggedEncodable {
51
+ constructor(private readonly envelope: Envelope) {}
52
+
53
+ cborTags(): ReturnType<typeof tagsForValues> {
54
+ return tagsForValues([TAG_ENVELOPE]);
55
+ }
56
+
57
+ untaggedCbor(): Cbor {
58
+ return this.envelope.untaggedCbor();
59
+ }
60
+
61
+ taggedCbor(): Cbor {
62
+ return this.envelope.taggedCbor();
63
+ }
64
+ }
65
+
66
+ /// Implements CborTaggedDecodable for Envelope.
67
+ ///
68
+ /// Provides the ability to decode an envelope from untagged CBOR.
69
+ export class EnvelopeCBORTaggedDecodable<T = Envelope> implements CborTaggedDecodable<T> {
70
+ cborTags(): ReturnType<typeof tagsForValues> {
71
+ return tagsForValues([TAG_ENVELOPE]);
72
+ }
73
+
74
+ static fromUntaggedCbor(cbor: Cbor): Envelope {
75
+ return Envelope.fromUntaggedCbor(cbor);
76
+ }
77
+
78
+ static fromTaggedCbor(cbor: Cbor): Envelope {
79
+ return Envelope.fromTaggedCbor(cbor);
80
+ }
81
+
82
+ fromUntaggedCbor(cbor: Cbor): T {
83
+ return Envelope.fromUntaggedCbor(cbor) as T;
84
+ }
85
+
86
+ fromTaggedCbor(cbor: Cbor): T {
87
+ return Envelope.fromTaggedCbor(cbor) as T;
88
+ }
89
+ }
90
+
91
+ /// Convenience function to convert an Envelope to CBOR.
92
+ ///
93
+ /// @param envelope - The envelope to convert
94
+ /// @returns The CBOR representation (tagged)
95
+ export function envelopeToCbor(envelope: Envelope): Cbor {
96
+ return envelope.taggedCbor();
97
+ }
98
+
99
+ /// Convenience function to create an Envelope from CBOR.
100
+ ///
101
+ /// @param cbor - The CBOR value (expected to be tagged with TAG_ENVELOPE)
102
+ /// @returns A new Envelope
103
+ export function envelopeFromCbor(cbor: Cbor): Envelope {
104
+ return Envelope.fromTaggedCbor(cbor);
105
+ }
106
+
107
+ /// Convenience function to encode an Envelope to CBOR bytes.
108
+ ///
109
+ /// @param envelope - The envelope to encode
110
+ /// @returns The CBOR bytes
111
+ export function envelopeToBytes(envelope: Envelope): Uint8Array {
112
+ return cborData(envelope.taggedCbor());
113
+ }
114
+
115
+ /// Convenience function to decode an Envelope from CBOR bytes.
116
+ ///
117
+ /// @param bytes - The CBOR bytes
118
+ /// @returns A new Envelope
119
+ export function envelopeFromBytes(bytes: Uint8Array): Envelope {
120
+ const cbor = decodeCbor(bytes);
121
+ return Envelope.fromTaggedCbor(cbor);
122
+ }