@cardanowall/poe-standard 0.9.0 → 0.11.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/README.md +45 -38
- package/dist/index.cjs +21 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -28
- package/dist/index.js.map +1 -1
- package/dist/validator.cjs +21 -27
- package/dist/validator.cjs.map +1 -1
- package/dist/validator.d.cts +29 -1
- package/dist/validator.d.ts +29 -1
- package/dist/validator.js +19 -28
- package/dist/validator.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,17 +19,7 @@ result, in any implementation, against shared test vectors.
|
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
22
|
-
The Label 309 TypeScript packages are pre-1.0 and not yet published to npm. Build from the workspace:
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
pnpm install
|
|
26
|
-
pnpm --filter @cardanowall/poe-standard typecheck
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Once published, the install will be:
|
|
30
|
-
|
|
31
22
|
```sh
|
|
32
|
-
# once published
|
|
33
23
|
npm install @cardanowall/poe-standard
|
|
34
24
|
```
|
|
35
25
|
|
|
@@ -58,7 +48,7 @@ const bytes: Uint8Array = encodePoeRecord(record);
|
|
|
58
48
|
import { validatePoeRecord } from '@cardanowall/poe-standard';
|
|
59
49
|
|
|
60
50
|
const result = validatePoeRecord(bytes);
|
|
61
|
-
if (result.
|
|
51
|
+
if (result.valid) {
|
|
62
52
|
// result.record is the decoded PoeRecord.
|
|
63
53
|
// result.info / result.warnings carry non-fatal issues (e.g. an
|
|
64
54
|
// unsupported-but-tolerated signature algorithm id).
|
|
@@ -73,7 +63,7 @@ if (result.ok) {
|
|
|
73
63
|
|
|
74
64
|
`validatePoeRecord` never throws: every failure — malformed/non-canonical CBOR, schema mismatch,
|
|
75
65
|
registry violation, bad URI, malformed signature entry — is returned as data in a discriminated
|
|
76
|
-
`
|
|
66
|
+
`ValidationResult` union. The result is sorted, stable, and identical across the TS/Python/Rust
|
|
77
67
|
implementations.
|
|
78
68
|
|
|
79
69
|
### Build the bytes a record-level signature covers
|
|
@@ -91,22 +81,36 @@ const body: Uint8Array = encodeRecordBodyForSigning(record);
|
|
|
91
81
|
|
|
92
82
|
## Records on chain: chunk → validate
|
|
93
83
|
|
|
94
|
-
The Cardano ledger caps every metadata byte string
|
|
95
|
-
|
|
96
|
-
is
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
The Cardano ledger caps every metadata byte string at 64 bytes, so the serialised record body crosses
|
|
85
|
+
the ledger as a **whole-body chunk array**: a CBOR array of ≤64-byte byte strings whose in-order
|
|
86
|
+
concatenation is the canonical body. This transport split is the _only_ chunking the format performs —
|
|
87
|
+
fields inside the reassembled body (URIs, COSE_Sign1 and COSE_Key blobs) are ordinary CBOR values with
|
|
88
|
+
no per-field chunk wrappers of their own. A verifier reassembles the array before calling
|
|
89
|
+
`validatePoeRecord`, so the validator only ever sees the single reconstructed body. Both directions of
|
|
90
|
+
the transport are exported from the root:
|
|
99
91
|
|
|
100
92
|
```ts
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
import {
|
|
94
|
+
chunkRecordBody,
|
|
95
|
+
encodeLabel309Value,
|
|
96
|
+
reassembleLabel309Value,
|
|
97
|
+
validatePoeRecord,
|
|
98
|
+
} from '@cardanowall/poe-standard';
|
|
99
|
+
|
|
100
|
+
// Producer: canonical body bytes → the chunk array stored under label 309.
|
|
101
|
+
const chunks = chunkRecordBody(bodyBytes); // Uint8Array[], each ≤ 64 bytes
|
|
102
|
+
const label309Value = encodeLabel309Value(bodyBytes); // the CBOR bytes of that array
|
|
103
|
+
|
|
104
|
+
// Verifier: raw label-309 value bytes → the body, before validation.
|
|
105
|
+
const reassembled = reassembleLabel309Value(label309Value);
|
|
106
|
+
if (reassembled.ok) {
|
|
107
|
+
const result = validatePoeRecord(reassembled.body);
|
|
108
|
+
// …handle result.valid
|
|
109
|
+
} else {
|
|
110
|
+
// reassembled.issue is a MALFORMED_CBOR / CHUNK_TOO_LARGE ValidationIssue.
|
|
111
|
+
}
|
|
105
112
|
```
|
|
106
113
|
|
|
107
|
-
`chunkUri` / `reconstructChunkedUri` do the same for the chunked URI arrays carried in
|
|
108
|
-
`items[i].uris` and `merkle[i].uris`, splitting on UTF-8 codepoint boundaries.
|
|
109
|
-
|
|
110
114
|
## API overview
|
|
111
115
|
|
|
112
116
|
The package root re-exports every group. Subpath imports are available for `./schema`, `./encoder`,
|
|
@@ -120,11 +124,12 @@ The package root re-exports every group. Subpath imports are available for `./sc
|
|
|
120
124
|
|
|
121
125
|
**Validate** (`@cardanowall/poe-standard/validator`)
|
|
122
126
|
|
|
123
|
-
- `validatePoeRecord(bytes)` — the structural pipeline: canonical decode → schema parse →
|
|
124
|
-
domain checks. Returns a discriminated `
|
|
127
|
+
- `validatePoeRecord(bytes, options?)` — the structural pipeline: canonical decode → schema parse →
|
|
128
|
+
cross-field domain checks. Returns a discriminated `ValidationResult` (`valid: true` with `record` /
|
|
129
|
+
`warnings?` / `info?`, or `valid: false` with `issues`); never throws.
|
|
125
130
|
- `validateCidProfile(cid)` — offline IPFS CID-profile parser (CIDv0 and the Label 309 CIDv1 multibase
|
|
126
131
|
/ multicodec / multihash profile).
|
|
127
|
-
- `type
|
|
132
|
+
- `type ValidationResult`, `type ValidationIssue`, `type ValidatorOptions`, `type ValidatorRole`.
|
|
128
133
|
|
|
129
134
|
**Error codes** (`@cardanowall/poe-standard/error-codes`)
|
|
130
135
|
|
|
@@ -138,22 +143,24 @@ The package root re-exports every group. Subpath imports are available for `./sc
|
|
|
138
143
|
**Schema + types** (`@cardanowall/poe-standard/schema`)
|
|
139
144
|
|
|
140
145
|
- Zod schemas for the full v1 wire surface: `PoeRecordSchema`, `ItemEntrySchema`,
|
|
141
|
-
`MerkleCommitSchema`, `EncryptionEnvelopeSchema`, `
|
|
142
|
-
`Argon2idParamsSchema`, `HashesMapSchema`, `HashDigestSchema`,
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`PassphraseBlock`, `Argon2idParams`, `HashesMap`, `UriChunkArray`, `ChunkedBytesArray`, `SigEntry`,
|
|
146
|
+
`MerkleCommitSchema`, `EncryptionEnvelopeSchema`, `EncScheme1Schema`, `EncOpaqueSchema`,
|
|
147
|
+
`SlotSchema`, `PassphraseBlockSchema`, `Argon2idParamsSchema`, `HashesMapSchema`, `HashDigestSchema`,
|
|
148
|
+
`UriSchema`, `SigEntrySchema`, `SupersedesSchema`, `VersionLiteralSchema`.
|
|
149
|
+
- Inferred types: `PoeRecord`, `ItemEntry`, `MerkleCommit`, `EncryptionEnvelope`, `EncScheme1`,
|
|
150
|
+
`EncOpaque`, `Slot`, `PassphraseBlock`, `Argon2idParams`, `HashesMap`, `Uri`, `SigEntry`,
|
|
147
151
|
`Supersedes`.
|
|
148
152
|
- Extension-key helpers: `TOP_LEVEL_BASE_KEYS`, `isExtensionKey`, `EXTENSION_KEY_VENDOR_RE`,
|
|
149
153
|
`EXTENSION_KEY_COMPANION_RE`.
|
|
150
154
|
|
|
151
|
-
**
|
|
155
|
+
**Carriage** (re-exported from the root)
|
|
152
156
|
|
|
153
|
-
- `
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- `
|
|
157
|
+
- `chunkRecordBody(body)` / `encodeLabel309Value(body)` — the producer side: split the canonical body
|
|
158
|
+
into the whole-body ≤64-byte transport chunk array, or serialise that array to the label-309 value
|
|
159
|
+
bytes.
|
|
160
|
+
- `reassembleLabel309Value(valueBytes)` — the consumer side: reassemble the label-309 value into the
|
|
161
|
+
record body, returning a `Label309ReassemblyResult` (`ok: true` with `body`, or `ok: false` with a
|
|
162
|
+
`MALFORMED_CBOR` / `CHUNK_TOO_LARGE` issue). Run before `validatePoeRecord`.
|
|
163
|
+
- `TRANSPORT_CHUNK_MAX_BYTES` (64), `type Label309ReassemblyResult`.
|
|
157
164
|
|
|
158
165
|
For the exhaustive surface, see `src/index.ts`.
|
|
159
166
|
|
package/dist/index.cjs
CHANGED
|
@@ -3021,46 +3021,37 @@ function checkUris(uris, basePath, issues) {
|
|
|
3021
3021
|
uris.forEach((uri, ui) => checkOneUri(uri, [...basePath, ui], issues));
|
|
3022
3022
|
}
|
|
3023
3023
|
function checkOneUri(uri, path, issues) {
|
|
3024
|
+
const rejection = fetchSetUriRejection(uri);
|
|
3025
|
+
if (rejection !== null) issues.push(issueOf("INVALID_URI", path, rejection));
|
|
3026
|
+
}
|
|
3027
|
+
function isArweaveTxid(body) {
|
|
3028
|
+
return /^[A-Za-z0-9_-]{43}$/.test(body);
|
|
3029
|
+
}
|
|
3030
|
+
function fetchSetUriRejection(uri) {
|
|
3024
3031
|
if (uri.includes("#")) {
|
|
3025
|
-
|
|
3026
|
-
issueOf("INVALID_URI", path, "URI contains a fragment identifier ('#'), which is forbidden")
|
|
3027
|
-
);
|
|
3028
|
-
return;
|
|
3032
|
+
return "URI contains a fragment identifier ('#'), which is forbidden";
|
|
3029
3033
|
}
|
|
3030
3034
|
const sepIdx = uri.indexOf("://");
|
|
3031
3035
|
if (sepIdx <= 0 || !/^[a-z][a-z0-9+.-]*$/i.test(uri.slice(0, sepIdx))) {
|
|
3032
|
-
|
|
3033
|
-
issueOf("INVALID_URI", path, "URI is not absolute (missing scheme://hierarchical-part)")
|
|
3034
|
-
);
|
|
3035
|
-
return;
|
|
3036
|
+
return "URI is not absolute (missing scheme://hierarchical-part)";
|
|
3036
3037
|
}
|
|
3037
3038
|
const scheme = uri.slice(0, sepIdx).toLowerCase();
|
|
3038
3039
|
const rest = uri.slice(sepIdx + "://".length);
|
|
3039
3040
|
if (scheme === "ar") {
|
|
3040
|
-
|
|
3041
|
-
issues.push(
|
|
3042
|
-
issueOf(
|
|
3043
|
-
"INVALID_URI",
|
|
3044
|
-
path,
|
|
3045
|
-
"ar:// URI does not match `^ar://[A-Za-z0-9_-]{43}$` (43-char base64url txid, no path/query/fragment)"
|
|
3046
|
-
)
|
|
3047
|
-
);
|
|
3048
|
-
}
|
|
3049
|
-
return;
|
|
3041
|
+
return isArweaveTxid(rest) ? null : "ar:// URI does not match `^ar://[A-Za-z0-9_-]{43}$` (43-char base64url txid, no path/query/fragment)";
|
|
3050
3042
|
}
|
|
3051
3043
|
if (scheme === "ipfs") {
|
|
3052
3044
|
const slashIdx = rest.indexOf("/");
|
|
3053
3045
|
const cid = slashIdx === -1 ? rest : rest.slice(0, slashIdx);
|
|
3054
|
-
|
|
3055
|
-
issues.push(
|
|
3056
|
-
issueOf("INVALID_URI", path, "ipfs:// URI is not a valid CID under the Label 309 profile")
|
|
3057
|
-
);
|
|
3058
|
-
}
|
|
3059
|
-
return;
|
|
3046
|
+
return validateCidProfile(cid) ? null : "ipfs:// URI is not a valid CID under the Label 309 profile";
|
|
3060
3047
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3048
|
+
return "unsupported URI scheme; v1 PoE URI set is {ar://, ipfs://}";
|
|
3049
|
+
}
|
|
3050
|
+
function isFetchSetUri(uri) {
|
|
3051
|
+
return fetchSetUriRejection(uri) === null;
|
|
3052
|
+
}
|
|
3053
|
+
function isArweaveTxUri(uri) {
|
|
3054
|
+
return uri.startsWith("ar://") && isArweaveTxid(uri.slice("ar://".length));
|
|
3064
3055
|
}
|
|
3065
3056
|
function checkItemEnc(item, idx, opts, issues) {
|
|
3066
3057
|
const encPath = ["items", idx, "enc"];
|
|
@@ -3870,7 +3861,10 @@ exports.encodeLabel309Value = encodeLabel309Value;
|
|
|
3870
3861
|
exports.encodePoeRecord = encodePoeRecord;
|
|
3871
3862
|
exports.encodeRecordBodyForSigning = encodeRecordBodyForSigning;
|
|
3872
3863
|
exports.errorCodeRegistryIndex = errorCodeRegistryIndex;
|
|
3864
|
+
exports.fetchSetUriRejection = fetchSetUriRejection;
|
|
3865
|
+
exports.isArweaveTxUri = isArweaveTxUri;
|
|
3873
3866
|
exports.isExtensionKey = isExtensionKey;
|
|
3867
|
+
exports.isFetchSetUri = isFetchSetUri;
|
|
3874
3868
|
exports.reassembleLabel309Value = reassembleLabel309Value;
|
|
3875
3869
|
exports.severityOf = severityOf;
|
|
3876
3870
|
exports.validateCidProfile = validateCidProfile;
|