@bitspark/archon 0.6.2 → 0.7.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.
@@ -20,12 +20,12 @@ export declare function sign(message: Uint8Array, seed: Uint8Array): Uint8Array;
20
20
  * @noble's `verify` instead throws on a wrong-length input, so we guard the lengths and
21
21
  * wrap the call. (Issue #90: found by fast-check fuzzing.)
22
22
  *
23
- * `{ zip215: false }` selects RFC 8032 semantics: @noble defaults to ZIP-215
24
- * (cofactored) verification, which *accepts* small-order public keys, whereas Go's
25
- * `crypto/ed25519` and Rust's `ed25519-dalek` v2 *reject* them. Passing `zip215: false`
26
- * makes the TS core reject small-order keys identically, restoring tri-core parity so
27
- * the canonical Rust core and the TS core admit exactly the same signed-fact / Head
28
- * evidence. (Issue #106.)
23
+ * `{ zip215: false }` selects @noble's strict decoding (canonical encodings only, no
24
+ * small-order key), which issue #106 chose in the belief that Go's `crypto/ed25519` and
25
+ * `ed25519-dalek` reject small-order keys. They do not — ADR 0008 measured it — and the
26
+ * flag alone does not settle the accepted set either way, because @noble's equation is
27
+ * cofactored and theirs is not. The profile check above is what settles it; the flag is
28
+ * kept because it is the stricter decoder and costs nothing.
29
29
  */
30
30
  export declare function verify(signature: Uint8Array, message: Uint8Array, pub: Uint8Array): boolean;
31
31
  /**
@@ -9,15 +9,49 @@
9
9
  // - `signInDomain` / `verifyInDomain` — DOMAIN-SEPARATED: Ed25519ph with a context
10
10
  // string (RFC 8032 §5.1), the domain, mixed into the hash. A signature made in one
11
11
  // domain verifies in no other and never as a raw signature, and a raw signature
12
- // verifies in no domain — cryptographically, whatever the bytes. One key, many
13
- // protocols, no cross-talk: the default for any protocol that has no bytes on disk yet.
14
- // The domain is the caller's (`<repo>/<purpose>/v<n>` by convention); archon neither
15
- // knows nor registers domains.
12
+ // verifies in no domain — for every key the profile admits, by the construction and
13
+ // not by any encoding convention. One key, many protocols, no cross-talk: the default
14
+ // for any protocol that has no bytes on disk yet. The domain is the caller's
15
+ // (`<repo>/<purpose>/v<n>` by convention); archon neither knows nor registers domains.
16
+ //
17
+ // Both verifies apply the verification profile (ADR 0008) before the equation: the
18
+ // public key and the signature's R must be canonical encodings of points of order
19
+ // exactly L — non-identity elements of the prime-order subgroup — and S must be in
20
+ // range. @noble with `{ zip215: false }` already refuses non-canonical encodings and
21
+ // small-order keys, but its equation is cofactored, so it accepts a mixed-order key for
22
+ // every message and a small-order R; Go and dalek, uncofactored, accept a mixed-order
23
+ // key for one message in eight and refuse a small-order R. The profile restricts the
24
+ // inputs to where the two equations agree, and is checked here explicitly so the
25
+ // accepted set is archon's, not the library's.
16
26
  //
17
27
  // Built on @noble/curves (which replaced @noble/ed25519 when domain separation landed:
18
28
  // only the former exposes the Ed25519ph-with-context variant). Both are synchronous, so
19
29
  // archon's LeafVerifier.verify stays synchronous, matching logos's sync SPI.
20
30
  import { ed25519, ed25519ph } from "@noble/curves/ed25519.js";
31
+ /**
32
+ * ADR 0008: `bytes` is a canonical encoding of a point of order exactly L. False for a
33
+ * point not on the curve, a non-canonical spelling (y ≥ p, or x = 0 with the sign bit
34
+ * set — @noble's strict decoder refuses both), the identity, any small-order point and
35
+ * any mixed-order point. Point arithmetic is the library's; this is a check.
36
+ */
37
+ function isPrimeOrderPoint(bytes) {
38
+ if (bytes.length !== PUBLIC_KEY_SIZE)
39
+ return false;
40
+ try {
41
+ const point = ed25519.Point.fromBytes(bytes, false);
42
+ return !point.is0() && point.isTorsionFree();
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ /** The profile's shape conditions on a (key, signature) pair. S's range is @noble's. */
49
+ function inProfile(pub, signature) {
50
+ return (signature.length === SIGNATURE_SIZE &&
51
+ pub.length === PUBLIC_KEY_SIZE &&
52
+ isPrimeOrderPoint(pub) &&
53
+ isPrimeOrderPoint(signature.subarray(0, PUBLIC_KEY_SIZE)));
54
+ }
21
55
  /** Length of an Ed25519 public key, in bytes. Mirrors rs `PUBLIC_KEY_SIZE` / go `PublicKeySize`. */
22
56
  export const PUBLIC_KEY_SIZE = 32;
23
57
  /** Length of an Ed25519 signature, in bytes. Mirrors rs `SIGNATURE_SIZE` / go `SignatureSize`. */
@@ -44,15 +78,15 @@ export function sign(message, seed) {
44
78
  * @noble's `verify` instead throws on a wrong-length input, so we guard the lengths and
45
79
  * wrap the call. (Issue #90: found by fast-check fuzzing.)
46
80
  *
47
- * `{ zip215: false }` selects RFC 8032 semantics: @noble defaults to ZIP-215
48
- * (cofactored) verification, which *accepts* small-order public keys, whereas Go's
49
- * `crypto/ed25519` and Rust's `ed25519-dalek` v2 *reject* them. Passing `zip215: false`
50
- * makes the TS core reject small-order keys identically, restoring tri-core parity so
51
- * the canonical Rust core and the TS core admit exactly the same signed-fact / Head
52
- * evidence. (Issue #106.)
81
+ * `{ zip215: false }` selects @noble's strict decoding (canonical encodings only, no
82
+ * small-order key), which issue #106 chose in the belief that Go's `crypto/ed25519` and
83
+ * `ed25519-dalek` reject small-order keys. They do not — ADR 0008 measured it — and the
84
+ * flag alone does not settle the accepted set either way, because @noble's equation is
85
+ * cofactored and theirs is not. The profile check above is what settles it; the flag is
86
+ * kept because it is the stricter decoder and costs nothing.
53
87
  */
54
88
  export function verify(signature, message, pub) {
55
- if (signature.length !== SIGNATURE_SIZE || pub.length !== PUBLIC_KEY_SIZE)
89
+ if (!inProfile(pub, signature))
56
90
  return false;
57
91
  try {
58
92
  return ed25519.verify(signature, message, pub, { zip215: false });
@@ -77,7 +111,7 @@ export function signInDomain(seed, domain, message) {
77
111
  * here.
78
112
  */
79
113
  export function verifyInDomain(pub, domain, message, signature) {
80
- if (signature.length !== SIGNATURE_SIZE || pub.length !== PUBLIC_KEY_SIZE)
114
+ if (!inProfile(pub, signature))
81
115
  return false;
82
116
  let context;
83
117
  try {
@@ -95,9 +129,14 @@ export function verifyInDomain(pub, domain, message, signature) {
95
129
  }
96
130
  /**
97
131
  * A domain is 1..=255 bytes of UTF-8 — the RFC 8032 context bound, with the empty
98
- * context excluded on purpose (see `signInDomain`).
132
+ * context excluded on purpose (see `signInDomain`). A string that is not well-formed
133
+ * (a lone surrogate) is refused rather than encoded: `TextEncoder` would silently
134
+ * substitute U+FFFD, and a domain that changed on the way into the hash is not the
135
+ * domain the caller named.
99
136
  */
100
137
  function domainBytes(domain) {
138
+ if (!isWellFormed(domain))
139
+ throw new Error("domain is not well-formed Unicode");
101
140
  const bytes = new TextEncoder().encode(domain);
102
141
  if (bytes.length === 0)
103
142
  throw new Error("domain is empty");
@@ -106,4 +145,23 @@ function domainBytes(domain) {
106
145
  }
107
146
  return bytes;
108
147
  }
148
+ /**
149
+ * `String.prototype.isWellFormed` without raising the compile target to ES2024: every
150
+ * high surrogate is followed by a low one, and no low surrogate stands alone.
151
+ */
152
+ function isWellFormed(s) {
153
+ for (let i = 0; i < s.length; i++) {
154
+ const c = s.charCodeAt(i);
155
+ if (c >= 0xd800 && c <= 0xdbff) {
156
+ const d = s.charCodeAt(i + 1);
157
+ if (!(d >= 0xdc00 && d <= 0xdfff))
158
+ return false;
159
+ i++;
160
+ }
161
+ else if (c >= 0xdc00 && c <= 0xdfff) {
162
+ return false;
163
+ }
164
+ }
165
+ return true;
166
+ }
109
167
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,EAAE;AACF,kCAAkC;AAClC,EAAE;AACF,wFAAwF;AACxF,uFAAuF;AACvF,yFAAyF;AACzF,kBAAkB;AAClB,mFAAmF;AACnF,qFAAqF;AACrF,kFAAkF;AAClF,sFAAsF;AACtF,wFAAwF;AACxF,+EAA+E;AAC/E,yFAAyF;AACzF,EAAE;AACF,mFAAmF;AACnF,kFAAkF;AAClF,mFAAmF;AACnF,qFAAqF;AACrF,wFAAwF;AACxF,sFAAsF;AACtF,qFAAqF;AACrF,iFAAiF;AACjF,+CAA+C;AAC/C,EAAE;AACF,uFAAuF;AACvF,wFAAwF;AACxF,6EAA6E;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,KAAiB;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,SAAS,CAAC,GAAe,EAAE,SAAqB;IACvD,OAAO,CACL,SAAS,CAAC,MAAM,KAAK,cAAc;QACnC,GAAG,CAAC,MAAM,KAAK,eAAe;QAC9B,iBAAiB,CAAC,GAAG,CAAC;QACtB,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAC1D,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,kGAAkG;AAClG,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,iGAAiG;AACjG,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,OAAO,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,IAAI,CAAC,OAAmB,EAAE,IAAgB;IACxD,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,MAAM,CAAC,SAAqB,EAAE,OAAmB,EAAE,GAAe;IAChF,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAE,MAAc,EAAE,OAAmB;IAChF,OAAO,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAe,EACf,MAAc,EACd,OAAmB,EACnB,SAAqB;IAErB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,OAAmB,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,MAAc;IACjC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC3D,IAAI,KAAK,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,aAAa,KAAK,CAAC,MAAM,eAAe,eAAe,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,CAAS;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChD,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;YACtC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitspark/archon",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "archon: Ed25519 primitives, the canonical key spelling, and the SPKI/PKCS-8 key codec. The identity floor beneath the law.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {