@bcts/envelope 1.0.0-alpha.5
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 +23 -0
- package/dist/index.cjs +2646 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +978 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +978 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +2644 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +2552 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
- package/src/base/assertion.ts +179 -0
- package/src/base/assertions.ts +304 -0
- package/src/base/cbor.ts +122 -0
- package/src/base/digest.ts +204 -0
- package/src/base/elide.ts +526 -0
- package/src/base/envelope-decodable.ts +229 -0
- package/src/base/envelope-encodable.ts +71 -0
- package/src/base/envelope.ts +790 -0
- package/src/base/error.ts +421 -0
- package/src/base/index.ts +56 -0
- package/src/base/leaf.ts +226 -0
- package/src/base/queries.ts +374 -0
- package/src/base/walk.ts +241 -0
- package/src/base/wrap.ts +72 -0
- package/src/extension/attachment.ts +369 -0
- package/src/extension/compress.ts +293 -0
- package/src/extension/encrypt.ts +379 -0
- package/src/extension/expression.ts +404 -0
- package/src/extension/index.ts +72 -0
- package/src/extension/proof.ts +276 -0
- package/src/extension/recipient.ts +557 -0
- package/src/extension/salt.ts +223 -0
- package/src/extension/signature.ts +463 -0
- package/src/extension/types.ts +222 -0
- package/src/format/diagnostic.ts +116 -0
- package/src/format/hex.ts +25 -0
- package/src/format/index.ts +13 -0
- package/src/format/tree.ts +168 -0
- package/src/index.ts +32 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/string.ts +48 -0
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bcts/envelope",
|
|
3
|
+
"version": "1.0.0-alpha.5",
|
|
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://github.com/leonardocustodio/blockchain-commons",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/leonardocustodio/blockchain-commons",
|
|
12
|
+
"directory": "packages/envelope"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/leonardocustodio/blockchain-commons/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
|
+
"browser": "./dist/index.iife.js",
|
|
27
|
+
"default": "./dist/index.mjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown",
|
|
37
|
+
"dev": "tsdown --watch",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
|
|
41
|
+
"lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"docs": "typedoc",
|
|
45
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"envelope",
|
|
49
|
+
"cbor",
|
|
50
|
+
"dcbor",
|
|
51
|
+
"gordian",
|
|
52
|
+
"blockchain-commons",
|
|
53
|
+
"privacy",
|
|
54
|
+
"encryption",
|
|
55
|
+
"deterministic",
|
|
56
|
+
"encoding",
|
|
57
|
+
"serialization"
|
|
58
|
+
],
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@bcts/eslint": "workspace:*",
|
|
64
|
+
"@bcts/tsconfig": "workspace:*",
|
|
65
|
+
"@eslint/js": "^9.39.1",
|
|
66
|
+
"@types/node": "^24.10.1",
|
|
67
|
+
"@types/pako": "^2.0.3",
|
|
68
|
+
"eslint": "^9.39.1",
|
|
69
|
+
"prettier": "^3.2.5",
|
|
70
|
+
"ts-node": "^10.9.2",
|
|
71
|
+
"tsdown": "^0.17.1",
|
|
72
|
+
"typedoc": "^0.28.15",
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"vitest": "^3.2.4"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@bcts/components": "workspace:*",
|
|
78
|
+
"@bcts/crypto": "workspace:*",
|
|
79
|
+
"@bcts/dcbor": "workspace:*",
|
|
80
|
+
"@bcts/known-values": "workspace:*",
|
|
81
|
+
"@bcts/rand": "workspace:*",
|
|
82
|
+
"@bcts/uniform-resources": "workspace:*",
|
|
83
|
+
"pako": "^2.1.0"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -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,304 @@
|
|
|
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
|
+
declare module "./envelope" {
|
|
15
|
+
interface Envelope {
|
|
16
|
+
/// Returns a new envelope with multiple assertion envelopes added.
|
|
17
|
+
///
|
|
18
|
+
/// This is a convenience method for adding multiple assertions at once.
|
|
19
|
+
/// Each assertion in the array must be a valid assertion envelope or an
|
|
20
|
+
/// obscured variant of one.
|
|
21
|
+
///
|
|
22
|
+
/// @param assertions - An array of valid assertion envelopes to add
|
|
23
|
+
/// @returns A new envelope with all the assertions added
|
|
24
|
+
/// @throws {EnvelopeError} If any of the provided envelopes are not valid
|
|
25
|
+
/// assertion envelopes
|
|
26
|
+
addAssertionEnvelopes(assertions: Envelope[]): Envelope;
|
|
27
|
+
|
|
28
|
+
/// Adds an optional assertion envelope to this envelope.
|
|
29
|
+
///
|
|
30
|
+
/// If the optional assertion is present, adds it to the envelope.
|
|
31
|
+
/// Otherwise, returns the envelope unchanged. This method is particularly
|
|
32
|
+
/// useful when working with functions that may or may not return an
|
|
33
|
+
/// assertion.
|
|
34
|
+
///
|
|
35
|
+
/// The method also ensures that duplicate assertions (with the same digest)
|
|
36
|
+
/// are not added, making it idempotent.
|
|
37
|
+
///
|
|
38
|
+
/// @param assertion - An optional assertion envelope to add
|
|
39
|
+
/// @returns A new envelope with the assertion added if provided, or the
|
|
40
|
+
/// original envelope if no assertion was provided or it was a duplicate
|
|
41
|
+
/// @throws {EnvelopeError} If the provided envelope is not a valid assertion
|
|
42
|
+
/// envelope or an obscured variant
|
|
43
|
+
addOptionalAssertionEnvelope(assertion: Envelope | undefined): Envelope;
|
|
44
|
+
|
|
45
|
+
/// Adds an assertion with the given predicate and optional object.
|
|
46
|
+
///
|
|
47
|
+
/// This method is useful when you have a predicate but may or may not have
|
|
48
|
+
/// an object value to associate with it. If the object is present, an
|
|
49
|
+
/// assertion is created and added to the envelope. Otherwise, the
|
|
50
|
+
/// envelope is returned unchanged.
|
|
51
|
+
///
|
|
52
|
+
/// @param predicate - The predicate for the assertion
|
|
53
|
+
/// @param object - An optional object value for the assertion
|
|
54
|
+
/// @returns A new envelope with the assertion added if the object was
|
|
55
|
+
/// provided, or the original envelope if no object was provided
|
|
56
|
+
addOptionalAssertion(
|
|
57
|
+
predicate: EnvelopeEncodableValue,
|
|
58
|
+
object: EnvelopeEncodableValue | undefined,
|
|
59
|
+
): Envelope;
|
|
60
|
+
|
|
61
|
+
/// Adds an assertion with the given predicate and string value, but only if
|
|
62
|
+
/// the string is non-empty.
|
|
63
|
+
///
|
|
64
|
+
/// This is a convenience method that only adds an assertion if the string
|
|
65
|
+
/// value is non-empty. It's particularly useful when working with user
|
|
66
|
+
/// input or optional text fields that should only be included if they
|
|
67
|
+
/// contain actual content.
|
|
68
|
+
///
|
|
69
|
+
/// @param predicate - The predicate for the assertion
|
|
70
|
+
/// @param str - The string value for the assertion
|
|
71
|
+
/// @returns A new envelope with the assertion added if the string is
|
|
72
|
+
/// non-empty, or the original envelope if the string is empty
|
|
73
|
+
addNonemptyStringAssertion(predicate: EnvelopeEncodableValue, str: string): Envelope;
|
|
74
|
+
|
|
75
|
+
/// Returns a new envelope with the given array of assertions added.
|
|
76
|
+
///
|
|
77
|
+
/// Similar to `addAssertionEnvelopes` but doesn't throw errors. This is
|
|
78
|
+
/// useful when you're certain all envelopes in the array are valid
|
|
79
|
+
/// assertion envelopes and don't need to handle errors.
|
|
80
|
+
///
|
|
81
|
+
/// @param envelopes - An array of assertion envelopes to add
|
|
82
|
+
/// @returns A new envelope with all the valid assertions added
|
|
83
|
+
addAssertions(envelopes: Envelope[]): Envelope;
|
|
84
|
+
|
|
85
|
+
/// Adds an assertion only if the provided condition is true.
|
|
86
|
+
///
|
|
87
|
+
/// This method allows for conditional inclusion of assertions based on a
|
|
88
|
+
/// boolean condition. It's a convenient way to add assertions only in
|
|
89
|
+
/// certain circumstances without requiring separate conditional logic.
|
|
90
|
+
///
|
|
91
|
+
/// @param condition - Boolean that determines whether to add the assertion
|
|
92
|
+
/// @param predicate - The predicate for the assertion
|
|
93
|
+
/// @param object - The object value for the assertion
|
|
94
|
+
/// @returns A new envelope with the assertion added if the condition is
|
|
95
|
+
/// true, or the original envelope if the condition is false
|
|
96
|
+
addAssertionIf(
|
|
97
|
+
condition: boolean,
|
|
98
|
+
predicate: EnvelopeEncodableValue,
|
|
99
|
+
object: EnvelopeEncodableValue,
|
|
100
|
+
): Envelope;
|
|
101
|
+
|
|
102
|
+
/// Adds an assertion envelope only if the provided condition is true.
|
|
103
|
+
///
|
|
104
|
+
/// Similar to `addAssertionIf` but works with pre-constructed assertion
|
|
105
|
+
/// envelopes. This is useful when you have already created an assertion
|
|
106
|
+
/// envelope separately and want to conditionally add it.
|
|
107
|
+
///
|
|
108
|
+
/// @param condition - Boolean that determines whether to add the assertion
|
|
109
|
+
/// envelope
|
|
110
|
+
/// @param assertionEnvelope - The assertion envelope to add
|
|
111
|
+
/// @returns A new envelope with the assertion added if the condition is
|
|
112
|
+
/// true, or the original envelope if the condition is false
|
|
113
|
+
/// @throws {EnvelopeError} If the provided envelope is not a valid assertion
|
|
114
|
+
/// envelope or an obscured variant and the condition is true
|
|
115
|
+
addAssertionEnvelopeIf(condition: boolean, assertionEnvelope: Envelope): Envelope;
|
|
116
|
+
|
|
117
|
+
/// Returns a new envelope with the given assertion removed.
|
|
118
|
+
///
|
|
119
|
+
/// Finds and removes an assertion matching the target assertion's digest.
|
|
120
|
+
/// If the assertion doesn't exist, returns the same envelope unchanged.
|
|
121
|
+
/// If removing the assertion would leave the envelope with no assertions,
|
|
122
|
+
/// returns just the subject as a new envelope.
|
|
123
|
+
///
|
|
124
|
+
/// @param target - The assertion envelope to remove
|
|
125
|
+
/// @returns A new envelope with the specified assertion removed if found,
|
|
126
|
+
/// or the original envelope if not found
|
|
127
|
+
removeAssertion(target: Envelope): Envelope;
|
|
128
|
+
|
|
129
|
+
/// Returns a new envelope with the given assertion replaced by a new one.
|
|
130
|
+
///
|
|
131
|
+
/// This method removes the specified assertion and adds a new one in its
|
|
132
|
+
/// place. If the targeted assertion does not exist, returns the same
|
|
133
|
+
/// envelope with the new assertion added.
|
|
134
|
+
///
|
|
135
|
+
/// @param assertion - The assertion envelope to replace
|
|
136
|
+
/// @param newAssertion - The new assertion envelope to add
|
|
137
|
+
/// @returns A new envelope with the assertion replaced if found, or the
|
|
138
|
+
/// original envelope with the new assertion added if not found
|
|
139
|
+
/// @throws {EnvelopeError} If the new assertion is not a valid assertion
|
|
140
|
+
/// envelope or an obscured variant
|
|
141
|
+
replaceAssertion(assertion: Envelope, newAssertion: Envelope): Envelope;
|
|
142
|
+
|
|
143
|
+
/// Returns a new envelope with its subject replaced by the provided one.
|
|
144
|
+
///
|
|
145
|
+
/// This method preserves all assertions from the original envelope but
|
|
146
|
+
/// applies them to a new subject. It effectively creates a new envelope
|
|
147
|
+
/// with the provided subject and copies over all assertions from the
|
|
148
|
+
/// current envelope.
|
|
149
|
+
///
|
|
150
|
+
/// @param subject - The new subject for the envelope
|
|
151
|
+
/// @returns A new envelope with the new subject and all assertions from the
|
|
152
|
+
/// original envelope
|
|
153
|
+
replaceSubject(subject: Envelope): Envelope;
|
|
154
|
+
|
|
155
|
+
/// Returns the assertions of this envelope.
|
|
156
|
+
///
|
|
157
|
+
/// For a node envelope, returns the array of assertion envelopes.
|
|
158
|
+
/// For all other envelope types, returns an empty array.
|
|
159
|
+
///
|
|
160
|
+
/// @returns An array of assertion envelopes
|
|
161
|
+
assertions(): Envelope[];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Implementation of addAssertionEnvelopes
|
|
166
|
+
Envelope.prototype.addAssertionEnvelopes = function (
|
|
167
|
+
this: Envelope,
|
|
168
|
+
assertions: Envelope[],
|
|
169
|
+
): Envelope {
|
|
170
|
+
return assertions.reduce((result, assertion) => result.addAssertionEnvelope(assertion), this);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/// Implementation of addOptionalAssertionEnvelope
|
|
174
|
+
Envelope.prototype.addOptionalAssertionEnvelope = function (
|
|
175
|
+
this: Envelope,
|
|
176
|
+
assertion: Envelope | undefined,
|
|
177
|
+
): Envelope {
|
|
178
|
+
if (assertion === undefined) {
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate that the assertion is a valid assertion or obscured envelope
|
|
183
|
+
if (!assertion.isSubjectAssertion() && !assertion.isSubjectObscured()) {
|
|
184
|
+
throw EnvelopeError.invalidFormat();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const c = this.case();
|
|
188
|
+
|
|
189
|
+
// Check if this is already a node
|
|
190
|
+
if (c.type === "node") {
|
|
191
|
+
// Check for duplicate assertions
|
|
192
|
+
const isDuplicate = c.assertions.some((a) => a.digest().equals(assertion.digest()));
|
|
193
|
+
if (isDuplicate) {
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Add the new assertion
|
|
198
|
+
return Envelope.newWithUncheckedAssertions(c.subject, [...c.assertions, assertion]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Otherwise, create a new node with this envelope as subject
|
|
202
|
+
return Envelope.newWithUncheckedAssertions(this.subject(), [assertion]);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/// Implementation of addOptionalAssertion
|
|
206
|
+
Envelope.prototype.addOptionalAssertion = function (
|
|
207
|
+
this: Envelope,
|
|
208
|
+
predicate: EnvelopeEncodableValue,
|
|
209
|
+
object: EnvelopeEncodableValue | undefined,
|
|
210
|
+
): Envelope {
|
|
211
|
+
if (object === undefined || object === null) {
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
return this.addAssertion(predicate, object);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/// Implementation of addNonemptyStringAssertion
|
|
218
|
+
Envelope.prototype.addNonemptyStringAssertion = function (
|
|
219
|
+
this: Envelope,
|
|
220
|
+
predicate: EnvelopeEncodableValue,
|
|
221
|
+
str: string,
|
|
222
|
+
): Envelope {
|
|
223
|
+
if (str.length === 0) {
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
return this.addAssertion(predicate, str);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/// Implementation of addAssertions
|
|
230
|
+
Envelope.prototype.addAssertions = function (this: Envelope, envelopes: Envelope[]): Envelope {
|
|
231
|
+
return envelopes.reduce((result, envelope) => result.addAssertionEnvelope(envelope), this);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/// Implementation of addAssertionIf
|
|
235
|
+
Envelope.prototype.addAssertionIf = function (
|
|
236
|
+
this: Envelope,
|
|
237
|
+
condition: boolean,
|
|
238
|
+
predicate: EnvelopeEncodableValue,
|
|
239
|
+
object: EnvelopeEncodableValue,
|
|
240
|
+
): Envelope {
|
|
241
|
+
if (condition) {
|
|
242
|
+
return this.addAssertion(predicate, object);
|
|
243
|
+
}
|
|
244
|
+
return this;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/// Implementation of addAssertionEnvelopeIf
|
|
248
|
+
Envelope.prototype.addAssertionEnvelopeIf = function (
|
|
249
|
+
this: Envelope,
|
|
250
|
+
condition: boolean,
|
|
251
|
+
assertionEnvelope: Envelope,
|
|
252
|
+
): Envelope {
|
|
253
|
+
if (condition) {
|
|
254
|
+
return this.addAssertionEnvelope(assertionEnvelope);
|
|
255
|
+
}
|
|
256
|
+
return this;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/// Implementation of removeAssertion
|
|
260
|
+
Envelope.prototype.removeAssertion = function (this: Envelope, target: Envelope): Envelope {
|
|
261
|
+
const assertions = this.assertions();
|
|
262
|
+
const targetDigest = target.digest();
|
|
263
|
+
|
|
264
|
+
const index = assertions.findIndex((a) => a.digest().equals(targetDigest));
|
|
265
|
+
|
|
266
|
+
if (index === -1) {
|
|
267
|
+
// Assertion not found, return unchanged
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Remove the assertion
|
|
272
|
+
const newAssertions = [...assertions.slice(0, index), ...assertions.slice(index + 1)];
|
|
273
|
+
|
|
274
|
+
if (newAssertions.length === 0) {
|
|
275
|
+
// No assertions left, return just the subject
|
|
276
|
+
return this.subject();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Return envelope with remaining assertions
|
|
280
|
+
return Envelope.newWithUncheckedAssertions(this.subject(), newAssertions);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/// Implementation of replaceAssertion
|
|
284
|
+
Envelope.prototype.replaceAssertion = function (
|
|
285
|
+
this: Envelope,
|
|
286
|
+
assertion: Envelope,
|
|
287
|
+
newAssertion: Envelope,
|
|
288
|
+
): Envelope {
|
|
289
|
+
return this.removeAssertion(assertion).addAssertionEnvelope(newAssertion);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/// Implementation of replaceSubject
|
|
293
|
+
Envelope.prototype.replaceSubject = function (this: Envelope, subject: Envelope): Envelope {
|
|
294
|
+
return this.assertions().reduce((e, a) => e.addAssertionEnvelope(a), subject);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/// Implementation of assertions
|
|
298
|
+
Envelope.prototype.assertions = function (this: Envelope): Envelope[] {
|
|
299
|
+
const c = this.case();
|
|
300
|
+
if (c.type === "node") {
|
|
301
|
+
return c.assertions;
|
|
302
|
+
}
|
|
303
|
+
return [];
|
|
304
|
+
};
|
package/src/base/cbor.ts
ADDED
|
@@ -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
|
+
}
|