@thehonestmachine/oath-core 0.1.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 +202 -0
- package/README.md +9 -0
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +46 -0
- package/dist/bytes.js.map +1 -0
- package/dist/ed25519.d.ts +11 -0
- package/dist/ed25519.d.ts.map +1 -0
- package/dist/ed25519.js +37 -0
- package/dist/ed25519.js.map +1 -0
- package/dist/entry.d.ts +27 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +183 -0
- package/dist/entry.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/hash.d.ts +3 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +9 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/jcs.d.ts +19 -0
- package/dist/jcs.d.ts.map +1 -0
- package/dist/jcs.js +63 -0
- package/dist/jcs.js.map +1 -0
- package/dist/merkle.d.ts +35 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +177 -0
- package/dist/merkle.js.map +1 -0
- package/dist/oath.d.ts +34 -0
- package/dist/oath.d.ts.map +1 -0
- package/dist/oath.js +128 -0
- package/dist/oath.js.map +1 -0
- package/dist/sth.d.ts +16 -0
- package/dist/sth.d.ts.map +1 -0
- package/dist/sth.js +95 -0
- package/dist/sth.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +17 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +65 -0
- package/dist/validate.js.map +1 -0
- package/package.json +55 -0
- package/src/bytes.ts +46 -0
- package/src/ed25519.ts +40 -0
- package/src/entry.ts +208 -0
- package/src/errors.ts +4 -0
- package/src/hash.ts +10 -0
- package/src/index.ts +82 -0
- package/src/jcs.ts +60 -0
- package/src/merkle.ts +184 -0
- package/src/oath.ts +148 -0
- package/src/sth.ts +126 -0
- package/src/types.ts +108 -0
- package/src/validate.ts +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ProtocolError } from './errors.js';
|
|
2
|
+
export { bytesToHex, hexToBytes, bytesEqual, concatBytes, utf8Bytes } from './bytes.js';
|
|
3
|
+
export { sha256, sha256Hex } from './hash.js';
|
|
4
|
+
export { canonicalize, canonicalBytes } from './jcs.js';
|
|
5
|
+
export { HANDLE_RE, isHandle, isTimestamp, isHex, isPositiveInteger, isNonNegativeInteger, isNonEmptyString, assertPlainObject, assertKeys, } from './validate.js';
|
|
6
|
+
export { edPublicKey, edSign, edVerify } from './ed25519.js';
|
|
7
|
+
export { EMPTY_ROOT, leafHash, nodeHash, rootFromLeafHashes, inclusionPath, consistencyPath, verifyInclusion, verifyConsistency, } from './merkle.js';
|
|
8
|
+
export { SPEC_VERSION, DEFAULT_MAX_LATENCY_SECONDS, assertValidOathDoc, assertValidUnsignedOathDoc, oathSigningInput, oathHash, signOathDoc, verifyOathSignature, assertValidAmendment, } from './oath.js';
|
|
9
|
+
export { RESERVED_ACTION_TYPES, assertValidEntry, assertValidUnsignedEntry, entrySigningInput, leafBytesOf, entryLeafHash, signEntry, verifyEntrySignature, assertEntryUnderOath, isPrompt, } from './entry.js';
|
|
10
|
+
export { assertValidSth, assertValidUnsignedSth, sthSigningInput, signSth, verifySthSignature, assertValidReceipt, assertValidUnsignedReceipt, receiptSigningInput, signReceipt, verifyReceiptSignature, } from './sth.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EACL,SAAS,EACT,QAAQ,EACR,WAAW,EACX,KAAK,EACL,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,YAAY,EACZ,2BAA2B,EAC3B,kBAAkB,EAClB,0BAA0B,EAC1B,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,oBAAoB,EACpB,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,OAAO,EACP,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,WAAW,EACX,sBAAsB,GACvB,MAAM,UAAU,CAAC"}
|
package/dist/jcs.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Canonicalization Scheme (RFC 8785).
|
|
3
|
+
*
|
|
4
|
+
* Deliberately stricter than JSON.stringify: undefined values, non-plain
|
|
5
|
+
* objects (Date, Map, class instances), functions, symbols, bigints,
|
|
6
|
+
* non-finite numbers, and array holes are protocol errors rather than
|
|
7
|
+
* silent coercions. Trust structures do not get repaired on the way to
|
|
8
|
+
* being hashed.
|
|
9
|
+
*
|
|
10
|
+
* Number and string serialization delegate to JSON.stringify, which
|
|
11
|
+
* implements exactly the ECMAScript serializations RFC 8785 requires
|
|
12
|
+
* (including -0 → "0" and lowercase \u escapes for control characters).
|
|
13
|
+
* Property keys sort by UTF-16 code units, which is the default behavior
|
|
14
|
+
* of Array.prototype.sort for strings.
|
|
15
|
+
*/
|
|
16
|
+
export declare function canonicalize(value: unknown): string;
|
|
17
|
+
/** UTF-8 bytes of the canonical serialization — what gets hashed and signed. */
|
|
18
|
+
export declare function canonicalBytes(value: unknown): Uint8Array;
|
|
19
|
+
//# sourceMappingURL=jcs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jcs.d.ts","sourceRoot":"","sources":["../src/jcs.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAoCnD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAEzD"}
|
package/dist/jcs.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ProtocolError } from './errors.js';
|
|
2
|
+
import { utf8Bytes } from './bytes.js';
|
|
3
|
+
/**
|
|
4
|
+
* JSON Canonicalization Scheme (RFC 8785).
|
|
5
|
+
*
|
|
6
|
+
* Deliberately stricter than JSON.stringify: undefined values, non-plain
|
|
7
|
+
* objects (Date, Map, class instances), functions, symbols, bigints,
|
|
8
|
+
* non-finite numbers, and array holes are protocol errors rather than
|
|
9
|
+
* silent coercions. Trust structures do not get repaired on the way to
|
|
10
|
+
* being hashed.
|
|
11
|
+
*
|
|
12
|
+
* Number and string serialization delegate to JSON.stringify, which
|
|
13
|
+
* implements exactly the ECMAScript serializations RFC 8785 requires
|
|
14
|
+
* (including -0 → "0" and lowercase \u escapes for control characters).
|
|
15
|
+
* Property keys sort by UTF-16 code units, which is the default behavior
|
|
16
|
+
* of Array.prototype.sort for strings.
|
|
17
|
+
*/
|
|
18
|
+
export function canonicalize(value) {
|
|
19
|
+
if (value === null)
|
|
20
|
+
return 'null';
|
|
21
|
+
switch (typeof value) {
|
|
22
|
+
case 'boolean':
|
|
23
|
+
return value ? 'true' : 'false';
|
|
24
|
+
case 'number':
|
|
25
|
+
if (!Number.isFinite(value))
|
|
26
|
+
throw new ProtocolError('cannot canonicalize non-finite number');
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
case 'string':
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
case 'object':
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
throw new ProtocolError(`cannot canonicalize value of type ${typeof value}`);
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
for (let i = 0; i < value.length; i++) {
|
|
38
|
+
if (!(i in value))
|
|
39
|
+
throw new ProtocolError('cannot canonicalize array with holes');
|
|
40
|
+
parts.push(canonicalize(value[i]));
|
|
41
|
+
}
|
|
42
|
+
return '[' + parts.join(',') + ']';
|
|
43
|
+
}
|
|
44
|
+
const proto = Object.getPrototypeOf(value);
|
|
45
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
46
|
+
throw new ProtocolError('cannot canonicalize non-plain object');
|
|
47
|
+
}
|
|
48
|
+
const record = value;
|
|
49
|
+
const keys = Object.keys(record).sort();
|
|
50
|
+
const parts = [];
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
const v = record[key];
|
|
53
|
+
if (v === undefined)
|
|
54
|
+
throw new ProtocolError(`cannot canonicalize undefined value at key "${key}"`);
|
|
55
|
+
parts.push(JSON.stringify(key) + ':' + canonicalize(v));
|
|
56
|
+
}
|
|
57
|
+
return '{' + parts.join(',') + '}';
|
|
58
|
+
}
|
|
59
|
+
/** UTF-8 bytes of the canonical serialization — what gets hashed and signed. */
|
|
60
|
+
export function canonicalBytes(value) {
|
|
61
|
+
return utf8Bytes(canonicalize(value));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=jcs.js.map
|
package/dist/jcs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jcs.js","sourceRoot":"","sources":["../src/jcs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAClC,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,MAAM,IAAI,aAAa,CAAC,uCAAuC,CAAC,CAAC;YAC9F,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,QAAQ;YACX,MAAM;QACR;YACE,MAAM,IAAI,aAAa,CAAC,qCAAqC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,aAAa,CAAC,sCAAsC,CAAC,CAAC;YACnF,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACrC,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjD,MAAM,IAAI,aAAa,CAAC,sCAAsC,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,IAAI,aAAa,CAAC,+CAA+C,GAAG,GAAG,CAAC,CAAC;QACpG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;AACrC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/merkle.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** MTH of the empty tree: SHA-256 of the empty string. */
|
|
2
|
+
export declare const EMPTY_ROOT: Uint8Array;
|
|
3
|
+
export declare function leafHash(leafBytes: Uint8Array): Uint8Array;
|
|
4
|
+
export declare function nodeHash(left: Uint8Array, right: Uint8Array): Uint8Array;
|
|
5
|
+
/** MTH over precomputed leaf hashes (leafHash of each entry's leaf bytes). */
|
|
6
|
+
export declare function rootFromLeafHashes(hashes: readonly Uint8Array[]): Uint8Array;
|
|
7
|
+
/** Audit path for the leaf at `leafIndex` — PATH(m, D[n]) of RFC 6962 §2.1.1. */
|
|
8
|
+
export declare function inclusionPath(leafIndex: number, hashes: readonly Uint8Array[]): Uint8Array[];
|
|
9
|
+
/** Consistency path from `oldSize` to the full list — PROOF(m, D[n]) of RFC 6962 §2.1.2. */
|
|
10
|
+
export declare function consistencyPath(oldSize: number, hashes: readonly Uint8Array[]): Uint8Array[];
|
|
11
|
+
/**
|
|
12
|
+
* Verify an inclusion proof (spec §6.1). Returns false — never throws — on
|
|
13
|
+
* any mismatch, so attacker-supplied proofs cannot crash a monitor.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verifyInclusion(options: {
|
|
16
|
+
leafHash: Uint8Array;
|
|
17
|
+
leafIndex: number;
|
|
18
|
+
treeSize: number;
|
|
19
|
+
auditPath: readonly Uint8Array[];
|
|
20
|
+
rootHash: Uint8Array;
|
|
21
|
+
}): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Verify a consistency proof (spec §6.2): the tree at `newSize` is an
|
|
24
|
+
* append-only extension of the tree at `oldSize`. Returns false — never
|
|
25
|
+
* throws — on any mismatch. A false result against two signature-valid
|
|
26
|
+
* STHs is oathbreaker proof (spec §6.3).
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyConsistency(options: {
|
|
29
|
+
oldSize: number;
|
|
30
|
+
newSize: number;
|
|
31
|
+
oldRoot: Uint8Array;
|
|
32
|
+
newRoot: Uint8Array;
|
|
33
|
+
path: readonly Uint8Array[];
|
|
34
|
+
}): boolean;
|
|
35
|
+
//# sourceMappingURL=merkle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merkle.d.ts","sourceRoot":"","sources":["../src/merkle.ts"],"names":[],"mappings":"AAcA,0DAA0D;AAC1D,eAAO,MAAM,UAAU,EAAE,UAAsC,CAAC;AAEhE,wBAAgB,QAAQ,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAE1D;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAExE;AAqBD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAI5E;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAY5F;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAc5F;AAgBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,UAAU,EAAE,CAAC;IACjC,QAAQ,EAAE,UAAU,CAAC;CACtB,GAAG,OAAO,CAsBV;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7B,GAAG,OAAO,CAmCV"}
|
package/dist/merkle.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ProtocolError } from './errors.js';
|
|
2
|
+
import { bytesEqual, concatBytes } from './bytes.js';
|
|
3
|
+
import { sha256 } from './hash.js';
|
|
4
|
+
/**
|
|
5
|
+
* RFC 6962-style Merkle tree (spec §4): SHA-256 with 0x00/0x01 domain
|
|
6
|
+
* separation, split at the largest power of two strictly less than n.
|
|
7
|
+
* Sizes and indices use plain number arithmetic (no 32-bit bitwise ops),
|
|
8
|
+
* so trees are correct up to 2^53 leaves.
|
|
9
|
+
*/
|
|
10
|
+
const LEAF_PREFIX = new Uint8Array([0x00]);
|
|
11
|
+
const NODE_PREFIX = new Uint8Array([0x01]);
|
|
12
|
+
/** MTH of the empty tree: SHA-256 of the empty string. */
|
|
13
|
+
export const EMPTY_ROOT = sha256(new Uint8Array(0));
|
|
14
|
+
export function leafHash(leafBytes) {
|
|
15
|
+
return sha256(concatBytes(LEAF_PREFIX, leafBytes));
|
|
16
|
+
}
|
|
17
|
+
export function nodeHash(left, right) {
|
|
18
|
+
return sha256(concatBytes(NODE_PREFIX, left, right));
|
|
19
|
+
}
|
|
20
|
+
function largestPowerOfTwoLessThan(n) {
|
|
21
|
+
let k = 1;
|
|
22
|
+
while (k * 2 < n)
|
|
23
|
+
k *= 2;
|
|
24
|
+
return k;
|
|
25
|
+
}
|
|
26
|
+
function assertHashList(hashes) {
|
|
27
|
+
for (const h of hashes) {
|
|
28
|
+
if (h.length !== 32)
|
|
29
|
+
throw new ProtocolError('leaf hashes must be 32 bytes');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function subRoot(hashes, lo, hi) {
|
|
33
|
+
const n = hi - lo;
|
|
34
|
+
if (n === 1)
|
|
35
|
+
return hashes[lo];
|
|
36
|
+
const k = largestPowerOfTwoLessThan(n);
|
|
37
|
+
return nodeHash(subRoot(hashes, lo, lo + k), subRoot(hashes, lo + k, hi));
|
|
38
|
+
}
|
|
39
|
+
/** MTH over precomputed leaf hashes (leafHash of each entry's leaf bytes). */
|
|
40
|
+
export function rootFromLeafHashes(hashes) {
|
|
41
|
+
assertHashList(hashes);
|
|
42
|
+
if (hashes.length === 0)
|
|
43
|
+
return EMPTY_ROOT;
|
|
44
|
+
return subRoot(hashes, 0, hashes.length);
|
|
45
|
+
}
|
|
46
|
+
/** Audit path for the leaf at `leafIndex` — PATH(m, D[n]) of RFC 6962 §2.1.1. */
|
|
47
|
+
export function inclusionPath(leafIndex, hashes) {
|
|
48
|
+
assertHashList(hashes);
|
|
49
|
+
if (!Number.isSafeInteger(leafIndex) || leafIndex < 0 || leafIndex >= hashes.length) {
|
|
50
|
+
throw new ProtocolError('leaf index out of range');
|
|
51
|
+
}
|
|
52
|
+
const path = (m, lo, hi) => {
|
|
53
|
+
if (hi - lo === 1)
|
|
54
|
+
return [];
|
|
55
|
+
const k = largestPowerOfTwoLessThan(hi - lo);
|
|
56
|
+
if (m < k)
|
|
57
|
+
return [...path(m, lo, lo + k), subRoot(hashes, lo + k, hi)];
|
|
58
|
+
return [...path(m - k, lo + k, hi), subRoot(hashes, lo, lo + k)];
|
|
59
|
+
};
|
|
60
|
+
return path(leafIndex, 0, hashes.length);
|
|
61
|
+
}
|
|
62
|
+
/** Consistency path from `oldSize` to the full list — PROOF(m, D[n]) of RFC 6962 §2.1.2. */
|
|
63
|
+
export function consistencyPath(oldSize, hashes) {
|
|
64
|
+
assertHashList(hashes);
|
|
65
|
+
if (!Number.isSafeInteger(oldSize) || oldSize < 0 || oldSize > hashes.length) {
|
|
66
|
+
throw new ProtocolError('old size out of range');
|
|
67
|
+
}
|
|
68
|
+
if (oldSize === 0 || oldSize === hashes.length)
|
|
69
|
+
return [];
|
|
70
|
+
const sub = (m, lo, hi, complete) => {
|
|
71
|
+
const n = hi - lo;
|
|
72
|
+
if (m === n)
|
|
73
|
+
return complete ? [] : [subRoot(hashes, lo, hi)];
|
|
74
|
+
const k = largestPowerOfTwoLessThan(n);
|
|
75
|
+
if (m <= k)
|
|
76
|
+
return [...sub(m, lo, lo + k, complete), subRoot(hashes, lo + k, hi)];
|
|
77
|
+
return [...sub(m - k, lo + k, hi, false), subRoot(hashes, lo, lo + k)];
|
|
78
|
+
};
|
|
79
|
+
return sub(oldSize, 0, hashes.length, true);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Size/index guard for the VERIFIERS, which must fail closed: a malformed
|
|
83
|
+
* size makes verification return false, never throw, so an attacker cannot
|
|
84
|
+
* crash a monitor with a bad proof (spec §6, §6.3). The path GENERATORS,
|
|
85
|
+
* which operate on trusted local data, throw on bad input instead.
|
|
86
|
+
*/
|
|
87
|
+
function isValidSize(value) {
|
|
88
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
89
|
+
}
|
|
90
|
+
function allHashes(path) {
|
|
91
|
+
return path.every((p) => p.length === 32);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Verify an inclusion proof (spec §6.1). Returns false — never throws — on
|
|
95
|
+
* any mismatch, so attacker-supplied proofs cannot crash a monitor.
|
|
96
|
+
*/
|
|
97
|
+
export function verifyInclusion(options) {
|
|
98
|
+
const { leafHash: leaf, leafIndex, treeSize, auditPath, rootHash } = options;
|
|
99
|
+
if (!isValidSize(leafIndex) || !isValidSize(treeSize))
|
|
100
|
+
return false;
|
|
101
|
+
if (leafIndex >= treeSize || leaf.length !== 32 || !allHashes(auditPath))
|
|
102
|
+
return false;
|
|
103
|
+
let fn = leafIndex;
|
|
104
|
+
let sn = treeSize - 1;
|
|
105
|
+
let hash = leaf;
|
|
106
|
+
for (const sibling of auditPath) {
|
|
107
|
+
if (sn === 0)
|
|
108
|
+
return false;
|
|
109
|
+
if (fn % 2 === 1 || fn === sn) {
|
|
110
|
+
hash = nodeHash(sibling, hash);
|
|
111
|
+
while (fn !== 0 && fn % 2 === 0) {
|
|
112
|
+
fn = Math.floor(fn / 2);
|
|
113
|
+
sn = Math.floor(sn / 2);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
hash = nodeHash(hash, sibling);
|
|
118
|
+
}
|
|
119
|
+
fn = Math.floor(fn / 2);
|
|
120
|
+
sn = Math.floor(sn / 2);
|
|
121
|
+
}
|
|
122
|
+
return sn === 0 && bytesEqual(hash, rootHash);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Verify a consistency proof (spec §6.2): the tree at `newSize` is an
|
|
126
|
+
* append-only extension of the tree at `oldSize`. Returns false — never
|
|
127
|
+
* throws — on any mismatch. A false result against two signature-valid
|
|
128
|
+
* STHs is oathbreaker proof (spec §6.3).
|
|
129
|
+
*/
|
|
130
|
+
export function verifyConsistency(options) {
|
|
131
|
+
const { oldSize, newSize, oldRoot, newRoot, path } = options;
|
|
132
|
+
if (!isValidSize(oldSize) || !isValidSize(newSize))
|
|
133
|
+
return false;
|
|
134
|
+
if (oldSize > newSize || oldRoot.length !== 32 || newRoot.length !== 32 || !allHashes(path)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (oldSize === newSize)
|
|
138
|
+
return path.length === 0 && bytesEqual(oldRoot, newRoot);
|
|
139
|
+
if (oldSize === 0)
|
|
140
|
+
return path.length === 0;
|
|
141
|
+
const nodes = isPowerOfTwo(oldSize) ? [oldRoot, ...path] : [...path];
|
|
142
|
+
if (nodes.length === 0)
|
|
143
|
+
return false;
|
|
144
|
+
let fn = oldSize - 1;
|
|
145
|
+
let sn = newSize - 1;
|
|
146
|
+
while (fn % 2 === 1) {
|
|
147
|
+
fn = Math.floor(fn / 2);
|
|
148
|
+
sn = Math.floor(sn / 2);
|
|
149
|
+
}
|
|
150
|
+
let oldHash = nodes[0];
|
|
151
|
+
let newHash = nodes[0];
|
|
152
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
153
|
+
const c = nodes[i];
|
|
154
|
+
if (sn === 0)
|
|
155
|
+
return false;
|
|
156
|
+
if (fn % 2 === 1 || fn === sn) {
|
|
157
|
+
oldHash = nodeHash(c, oldHash);
|
|
158
|
+
newHash = nodeHash(c, newHash);
|
|
159
|
+
while (fn !== 0 && fn % 2 === 0) {
|
|
160
|
+
fn = Math.floor(fn / 2);
|
|
161
|
+
sn = Math.floor(sn / 2);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
newHash = nodeHash(newHash, c);
|
|
166
|
+
}
|
|
167
|
+
fn = Math.floor(fn / 2);
|
|
168
|
+
sn = Math.floor(sn / 2);
|
|
169
|
+
}
|
|
170
|
+
return sn === 0 && bytesEqual(oldHash, oldRoot) && bytesEqual(newHash, newRoot);
|
|
171
|
+
}
|
|
172
|
+
function isPowerOfTwo(n) {
|
|
173
|
+
while (n % 2 === 0)
|
|
174
|
+
n /= 2;
|
|
175
|
+
return n === 1;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=merkle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merkle.js","sourceRoot":"","sources":["../src/merkle.ts"],"names":[],"mappings":"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"}
|
package/dist/oath.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OathDoc, UnsignedOathDoc } from './types.js';
|
|
2
|
+
/** The spec_version this implementation conforms to (SPEC.md §12). */
|
|
3
|
+
export declare const SPEC_VERSION = "0.1";
|
|
4
|
+
/** Default confession latency commitment in seconds (SPEC.md §2.2, §8.2). */
|
|
5
|
+
export declare const DEFAULT_MAX_LATENCY_SECONDS = 300;
|
|
6
|
+
/** Validate a complete signed oath document (SPEC.md §2.2). Structural only. */
|
|
7
|
+
export declare function assertValidOathDoc(value: unknown): asserts value is OathDoc;
|
|
8
|
+
/** Validate an oath document that has not been signed yet. */
|
|
9
|
+
export declare function assertValidUnsignedOathDoc(value: unknown): asserts value is UnsignedOathDoc;
|
|
10
|
+
/** Canonical bytes of the document without its signature — the signing input. */
|
|
11
|
+
export declare function oathSigningInput(doc: UnsignedOathDoc | OathDoc): Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* The oath hash: SHA-256 (hex) of the canonical bytes of the complete signed
|
|
14
|
+
* document. Identifies this oath version everywhere in the protocol.
|
|
15
|
+
*/
|
|
16
|
+
export declare function oathHash(doc: OathDoc): string;
|
|
17
|
+
/**
|
|
18
|
+
* Sign an oath document. For version 1 the signing seed is the agent's own
|
|
19
|
+
* key; for an amendment it is the previous version's key (SPEC.md §2.2, §9).
|
|
20
|
+
*/
|
|
21
|
+
export declare function signOathDoc(unsigned: UnsignedOathDoc, signingSeedHex: string): OathDoc;
|
|
22
|
+
/**
|
|
23
|
+
* Verify an oath document's signature. `signerPubkeyHex` defaults to the
|
|
24
|
+
* document's own pubkey (correct for version 1); for an amendment pass the
|
|
25
|
+
* previous version's pubkey.
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyOathSignature(doc: OathDoc, signerPubkeyHex?: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Validate that `next` is a conforming amendment of `prev` (SPEC.md §9):
|
|
30
|
+
* same agent, version incremented by exactly 1, signed by the previous
|
|
31
|
+
* version's key.
|
|
32
|
+
*/
|
|
33
|
+
export declare function assertValidAmendment(prev: OathDoc, next: OathDoc): void;
|
|
34
|
+
//# sourceMappingURL=oath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oath.d.ts","sourceRoot":"","sources":["../src/oath.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAiE/C,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAE3E;AAED,8DAA8D;AAC9D,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAE3F;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,GAAG,UAAU,CAI3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAG7C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAItF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAYvE"}
|
package/dist/oath.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ProtocolError } from './errors.js';
|
|
2
|
+
import { canonicalBytes } from './jcs.js';
|
|
3
|
+
import { sha256Hex } from './hash.js';
|
|
4
|
+
import { edSign, edVerify } from './ed25519.js';
|
|
5
|
+
import { assertKeys, assertPlainObject, isHandle, isHex, isNonEmptyString, isPositiveInteger, isTimestamp, } from './validate.js';
|
|
6
|
+
/** The spec_version this implementation conforms to (SPEC.md §12). */
|
|
7
|
+
export const SPEC_VERSION = '0.1';
|
|
8
|
+
/** Default confession latency commitment in seconds (SPEC.md §2.2, §8.2). */
|
|
9
|
+
export const DEFAULT_MAX_LATENCY_SECONDS = 300;
|
|
10
|
+
const REQUIRED = ['spec_version', 'agent', 'version', 'pubkey', 'scope', 'laws', 'created_at'];
|
|
11
|
+
const OPTIONAL = ['display_name', 'commitments'];
|
|
12
|
+
const COMMITMENT_KEYS = ['entries_per_30d', 'max_latency_seconds'];
|
|
13
|
+
function validateOathShape(value, requireSignature) {
|
|
14
|
+
const doc = assertPlainObject(value, 'oath document');
|
|
15
|
+
const required = requireSignature ? [...REQUIRED, 'signature'] : REQUIRED;
|
|
16
|
+
const optional = requireSignature ? OPTIONAL : [...OPTIONAL, 'signature'];
|
|
17
|
+
assertKeys(doc, 'oath document', required, optional);
|
|
18
|
+
if (doc['spec_version'] !== SPEC_VERSION) {
|
|
19
|
+
throw new ProtocolError(`oath document spec_version must be "${SPEC_VERSION}"`);
|
|
20
|
+
}
|
|
21
|
+
if (!isHandle(doc['agent']))
|
|
22
|
+
throw new ProtocolError('oath document agent must be a valid handle');
|
|
23
|
+
if ('display_name' in doc && !isNonEmptyString(doc['display_name'])) {
|
|
24
|
+
throw new ProtocolError('oath document display_name must be a non-empty string');
|
|
25
|
+
}
|
|
26
|
+
if (!isPositiveInteger(doc['version'])) {
|
|
27
|
+
throw new ProtocolError('oath document version must be an integer >= 1');
|
|
28
|
+
}
|
|
29
|
+
if (!isHex(doc['pubkey'], 32)) {
|
|
30
|
+
throw new ProtocolError('oath document pubkey must be 32 bytes of lowercase hex');
|
|
31
|
+
}
|
|
32
|
+
if (!isNonEmptyString(doc['scope'])) {
|
|
33
|
+
throw new ProtocolError('oath document scope must be a non-empty string');
|
|
34
|
+
}
|
|
35
|
+
if ('commitments' in doc) {
|
|
36
|
+
const commitments = assertPlainObject(doc['commitments'], 'oath commitments');
|
|
37
|
+
assertKeys(commitments, 'oath commitments', [], COMMITMENT_KEYS);
|
|
38
|
+
for (const key of COMMITMENT_KEYS) {
|
|
39
|
+
if (key in commitments && !isPositiveInteger(commitments[key])) {
|
|
40
|
+
throw new ProtocolError(`oath commitments ${key} must be an integer >= 1`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
validateLaws(doc['laws']);
|
|
45
|
+
if (!isTimestamp(doc['created_at'])) {
|
|
46
|
+
throw new ProtocolError('oath document created_at must be an RFC 3339 UTC timestamp');
|
|
47
|
+
}
|
|
48
|
+
if (requireSignature && !isHex(doc['signature'], 64)) {
|
|
49
|
+
throw new ProtocolError('oath document signature must be 64 bytes of lowercase hex');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function validateLaws(value) {
|
|
53
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
54
|
+
throw new ProtocolError('oath document laws must be a non-empty array');
|
|
55
|
+
}
|
|
56
|
+
value.forEach((law, i) => {
|
|
57
|
+
const obj = assertPlainObject(law, `law at index ${i}`);
|
|
58
|
+
assertKeys(obj, `law at index ${i}`, ['n', 'mode', 'text'], []);
|
|
59
|
+
if (obj['n'] !== i + 1) {
|
|
60
|
+
throw new ProtocolError('laws must be numbered 1..N consecutively');
|
|
61
|
+
}
|
|
62
|
+
if (obj['mode'] !== 'machine' && obj['mode'] !== 'judgment') {
|
|
63
|
+
throw new ProtocolError('law mode must be "machine" or "judgment"');
|
|
64
|
+
}
|
|
65
|
+
if (!isNonEmptyString(obj['text'])) {
|
|
66
|
+
throw new ProtocolError('law text must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Validate a complete signed oath document (SPEC.md §2.2). Structural only. */
|
|
71
|
+
export function assertValidOathDoc(value) {
|
|
72
|
+
validateOathShape(value, true);
|
|
73
|
+
}
|
|
74
|
+
/** Validate an oath document that has not been signed yet. */
|
|
75
|
+
export function assertValidUnsignedOathDoc(value) {
|
|
76
|
+
validateOathShape(value, false);
|
|
77
|
+
}
|
|
78
|
+
/** Canonical bytes of the document without its signature — the signing input. */
|
|
79
|
+
export function oathSigningInput(doc) {
|
|
80
|
+
const { signature: _signature, ...unsigned } = doc;
|
|
81
|
+
assertValidUnsignedOathDoc(unsigned);
|
|
82
|
+
return canonicalBytes(unsigned);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The oath hash: SHA-256 (hex) of the canonical bytes of the complete signed
|
|
86
|
+
* document. Identifies this oath version everywhere in the protocol.
|
|
87
|
+
*/
|
|
88
|
+
export function oathHash(doc) {
|
|
89
|
+
assertValidOathDoc(doc);
|
|
90
|
+
return sha256Hex(canonicalBytes(doc));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sign an oath document. For version 1 the signing seed is the agent's own
|
|
94
|
+
* key; for an amendment it is the previous version's key (SPEC.md §2.2, §9).
|
|
95
|
+
*/
|
|
96
|
+
export function signOathDoc(unsigned, signingSeedHex) {
|
|
97
|
+
assertValidUnsignedOathDoc(unsigned);
|
|
98
|
+
const signature = edSign(canonicalBytes(unsigned), signingSeedHex);
|
|
99
|
+
return { ...unsigned, signature };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Verify an oath document's signature. `signerPubkeyHex` defaults to the
|
|
103
|
+
* document's own pubkey (correct for version 1); for an amendment pass the
|
|
104
|
+
* previous version's pubkey.
|
|
105
|
+
*/
|
|
106
|
+
export function verifyOathSignature(doc, signerPubkeyHex) {
|
|
107
|
+
assertValidOathDoc(doc);
|
|
108
|
+
return edVerify(doc.signature, oathSigningInput(doc), signerPubkeyHex ?? doc.pubkey);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate that `next` is a conforming amendment of `prev` (SPEC.md §9):
|
|
112
|
+
* same agent, version incremented by exactly 1, signed by the previous
|
|
113
|
+
* version's key.
|
|
114
|
+
*/
|
|
115
|
+
export function assertValidAmendment(prev, next) {
|
|
116
|
+
assertValidOathDoc(prev);
|
|
117
|
+
assertValidOathDoc(next);
|
|
118
|
+
if (next.agent !== prev.agent) {
|
|
119
|
+
throw new ProtocolError('amendment must keep the same agent handle');
|
|
120
|
+
}
|
|
121
|
+
if (next.version !== prev.version + 1) {
|
|
122
|
+
throw new ProtocolError('amendment must increment version by exactly 1');
|
|
123
|
+
}
|
|
124
|
+
if (!verifyOathSignature(next, prev.pubkey)) {
|
|
125
|
+
throw new ProtocolError("amendment signature must verify against the previous version's key");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=oath.js.map
|
package/dist/oath.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oath.js","sourceRoot":"","sources":["../src/oath.ts"],"names":[],"mappings":"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"}
|
package/dist/sth.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Receipt, SignedTreeHead, UnsignedReceipt, UnsignedTreeHead } from './types.js';
|
|
2
|
+
/** Validate a signed tree head (SPEC.md §5). Structural only. */
|
|
3
|
+
export declare function assertValidSth(value: unknown): asserts value is SignedTreeHead;
|
|
4
|
+
export declare function assertValidUnsignedSth(value: unknown): asserts value is UnsignedTreeHead;
|
|
5
|
+
/** Canonical bytes of the STH without its signature — the signing input. */
|
|
6
|
+
export declare function sthSigningInput(sth: UnsignedTreeHead | SignedTreeHead): Uint8Array;
|
|
7
|
+
export declare function signSth(unsigned: UnsignedTreeHead, ledgerSeedHex: string): SignedTreeHead;
|
|
8
|
+
export declare function verifySthSignature(sth: SignedTreeHead, ledgerPubkeyHex: string): boolean;
|
|
9
|
+
/** Validate a receipt (SPEC.md §11.3). Structural only. */
|
|
10
|
+
export declare function assertValidReceipt(value: unknown): asserts value is Receipt;
|
|
11
|
+
export declare function assertValidUnsignedReceipt(value: unknown): asserts value is UnsignedReceipt;
|
|
12
|
+
/** Canonical bytes of the receipt without its signature — the signing input. */
|
|
13
|
+
export declare function receiptSigningInput(receipt: UnsignedReceipt | Receipt): Uint8Array;
|
|
14
|
+
export declare function signReceipt(unsigned: UnsignedReceipt, ledgerSeedHex: string): Receipt;
|
|
15
|
+
export declare function verifyReceiptSignature(receipt: Receipt, ledgerPubkeyHex: string): boolean;
|
|
16
|
+
//# sourceMappingURL=sth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sth.d.ts","sourceRoot":"","sources":["../src/sth.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiC7F,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAE9E;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,gBAAgB,CAExF;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,GAAG,EAAE,gBAAgB,GAAG,cAAc,GAAG,UAAU,CAIlF;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,cAAc,CAIzF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAGxF;AA4BD,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAE3E;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAE3F;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,GAAG,UAAU,CAIlF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAIrF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAGzF"}
|
package/dist/sth.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ProtocolError } from './errors.js';
|
|
2
|
+
import { canonicalBytes } from './jcs.js';
|
|
3
|
+
import { bytesToHex } from './bytes.js';
|
|
4
|
+
import { EMPTY_ROOT } from './merkle.js';
|
|
5
|
+
import { edSign, edVerify } from './ed25519.js';
|
|
6
|
+
import { assertKeys, assertPlainObject, isHex, isNonEmptyString, isNonNegativeInteger, isPositiveInteger, isTimestamp, } from './validate.js';
|
|
7
|
+
const EMPTY_ROOT_HEX = bytesToHex(EMPTY_ROOT);
|
|
8
|
+
function validateSthShape(value, requireSignature) {
|
|
9
|
+
const sth = assertPlainObject(value, 'signed tree head');
|
|
10
|
+
const required = ['ledger', 'tree_size', 'root_hash', 'timestamp'];
|
|
11
|
+
assertKeys(sth, 'signed tree head', requireSignature ? [...required, 'signature'] : required, requireSignature ? [] : ['signature']);
|
|
12
|
+
if (!isNonEmptyString(sth['ledger'])) {
|
|
13
|
+
throw new ProtocolError('signed tree head ledger must be a non-empty string');
|
|
14
|
+
}
|
|
15
|
+
if (!isNonNegativeInteger(sth['tree_size'])) {
|
|
16
|
+
throw new ProtocolError('signed tree head tree_size must be a non-negative integer');
|
|
17
|
+
}
|
|
18
|
+
if (!isHex(sth['root_hash'], 32)) {
|
|
19
|
+
throw new ProtocolError('signed tree head root_hash must be 32 bytes of lowercase hex');
|
|
20
|
+
}
|
|
21
|
+
if (sth['tree_size'] === 0 && sth['root_hash'] !== EMPTY_ROOT_HEX) {
|
|
22
|
+
throw new ProtocolError('an empty tree has exactly one possible root hash (SPEC.md §4)');
|
|
23
|
+
}
|
|
24
|
+
if (!isTimestamp(sth['timestamp'])) {
|
|
25
|
+
throw new ProtocolError('signed tree head timestamp must be an RFC 3339 UTC timestamp');
|
|
26
|
+
}
|
|
27
|
+
if (requireSignature && !isHex(sth['signature'], 64)) {
|
|
28
|
+
throw new ProtocolError('signed tree head signature must be 64 bytes of lowercase hex');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Validate a signed tree head (SPEC.md §5). Structural only. */
|
|
32
|
+
export function assertValidSth(value) {
|
|
33
|
+
validateSthShape(value, true);
|
|
34
|
+
}
|
|
35
|
+
export function assertValidUnsignedSth(value) {
|
|
36
|
+
validateSthShape(value, false);
|
|
37
|
+
}
|
|
38
|
+
/** Canonical bytes of the STH without its signature — the signing input. */
|
|
39
|
+
export function sthSigningInput(sth) {
|
|
40
|
+
const { signature: _signature, ...unsigned } = sth;
|
|
41
|
+
assertValidUnsignedSth(unsigned);
|
|
42
|
+
return canonicalBytes(unsigned);
|
|
43
|
+
}
|
|
44
|
+
export function signSth(unsigned, ledgerSeedHex) {
|
|
45
|
+
assertValidUnsignedSth(unsigned);
|
|
46
|
+
const signature = edSign(canonicalBytes(unsigned), ledgerSeedHex);
|
|
47
|
+
return { ...unsigned, signature };
|
|
48
|
+
}
|
|
49
|
+
export function verifySthSignature(sth, ledgerPubkeyHex) {
|
|
50
|
+
assertValidSth(sth);
|
|
51
|
+
return edVerify(sth.signature, sthSigningInput(sth), ledgerPubkeyHex);
|
|
52
|
+
}
|
|
53
|
+
function validateReceiptShape(value, requireSignature) {
|
|
54
|
+
const receipt = assertPlainObject(value, 'receipt');
|
|
55
|
+
const required = ['ledger', 'leaf_hash', 'received_at', 'mmd_seconds'];
|
|
56
|
+
assertKeys(receipt, 'receipt', requireSignature ? [...required, 'signature'] : required, requireSignature ? [] : ['signature']);
|
|
57
|
+
if (!isNonEmptyString(receipt['ledger'])) {
|
|
58
|
+
throw new ProtocolError('receipt ledger must be a non-empty string');
|
|
59
|
+
}
|
|
60
|
+
if (!isHex(receipt['leaf_hash'], 32)) {
|
|
61
|
+
throw new ProtocolError('receipt leaf_hash must be 32 bytes of lowercase hex');
|
|
62
|
+
}
|
|
63
|
+
if (!isTimestamp(receipt['received_at'])) {
|
|
64
|
+
throw new ProtocolError('receipt received_at must be an RFC 3339 UTC timestamp');
|
|
65
|
+
}
|
|
66
|
+
if (!isPositiveInteger(receipt['mmd_seconds'])) {
|
|
67
|
+
throw new ProtocolError('receipt mmd_seconds must be an integer >= 1');
|
|
68
|
+
}
|
|
69
|
+
if (requireSignature && !isHex(receipt['signature'], 64)) {
|
|
70
|
+
throw new ProtocolError('receipt signature must be 64 bytes of lowercase hex');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Validate a receipt (SPEC.md §11.3). Structural only. */
|
|
74
|
+
export function assertValidReceipt(value) {
|
|
75
|
+
validateReceiptShape(value, true);
|
|
76
|
+
}
|
|
77
|
+
export function assertValidUnsignedReceipt(value) {
|
|
78
|
+
validateReceiptShape(value, false);
|
|
79
|
+
}
|
|
80
|
+
/** Canonical bytes of the receipt without its signature — the signing input. */
|
|
81
|
+
export function receiptSigningInput(receipt) {
|
|
82
|
+
const { signature: _signature, ...unsigned } = receipt;
|
|
83
|
+
assertValidUnsignedReceipt(unsigned);
|
|
84
|
+
return canonicalBytes(unsigned);
|
|
85
|
+
}
|
|
86
|
+
export function signReceipt(unsigned, ledgerSeedHex) {
|
|
87
|
+
assertValidUnsignedReceipt(unsigned);
|
|
88
|
+
const signature = edSign(canonicalBytes(unsigned), ledgerSeedHex);
|
|
89
|
+
return { ...unsigned, signature };
|
|
90
|
+
}
|
|
91
|
+
export function verifyReceiptSignature(receipt, ledgerPubkeyHex) {
|
|
92
|
+
assertValidReceipt(receipt);
|
|
93
|
+
return edVerify(receipt.signature, receiptSigningInput(receipt), ledgerPubkeyHex);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sth.js.map
|