Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build-and-test:
name: Build & Test
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x, 22.x]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Type check
run: npx tsc --noEmit

- name: Build
run: npm run build

- name: Test
run: npm test

publish-check:
name: Publish Dry Run
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: build-and-test

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22.x"
cache: "npm"
registry-url: "https://registry.npmjs.org"

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Publish dry run
run: npm publish --dry-run
75 changes: 66 additions & 9 deletions dist/crypto.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,69 @@
/** sha256 hex digest of UTF-8 text */
export declare function sha256HexUtf8(text: string): string;
/**
* Ed25519 signature over UTF-8 bytes of `message` (string).
* Node's crypto.sign/verify for Ed25519 uses `null` as the algorithm.
* @commandlayer/runtime-core — crypto.ts
*
* PROTOCOL SIGNING CONTRACT (canonical, locked to ENS production record):
* cl.sig.canonical = json.sorted_keys.v1
* cl.sig.pub = ed25519:<standard_base64_raw32> (= padding, +/ charset)
* signing message = Ed25519.sign(raw_canonical_utf8_bytes)
* where canonical = canonicalizeSortedKeysV1(payload)
*
* DO NOT change the signing message without a protocol version bump and
* coordinated migration across all repos.
*/
export declare function signEd25519MessageBase64(message: string, privateKeyPem: string): string;
export declare function verifyEd25519MessageBase64(message: string, signatureB64: string, publicKeyPemOrDer: string): boolean;
/** base64url helpers for compatibility */
export declare function base64UrlToBase64(b64url: string): string;
export declare function base64ToBase64Url(b64: string): string;
export declare const PROTOCOL_VERSION: "1.1.0";
export declare const CANONICAL_METHOD: "json.sorted_keys.v1";
export declare const SIGNATURE_ALG: "ed25519";
/** The ENS text record key for the signer's public key. */
export declare const ENS_KEY_PUB: "cl.sig.pub";
export declare const ENS_KEY_KID: "cl.sig.kid";
export declare const ENS_KEY_CANONICAL: "cl.sig.canonical";
export declare const ENS_KEY_SIGNER: "cl.receipt.signer";
/**
* Encode a raw 32-byte Ed25519 public key to the ENS cl.sig.pub format:
* ed25519:<standard_base64>
*
* Standard base64: uses A-Z a-z 0-9 +/ with = padding.
* This matches the live ENS record for runtime.commandlayer.eth.
*/
export declare function encodePublicKey(rawBytes: Uint8Array): string;
/**
* Parse an ENS cl.sig.pub value to raw 32-byte public key.
* Accepts: ed25519:<standard_base64>
* Rejects anything that isn't 32 bytes after decode.
*/
export declare function parsePublicKey(ensPubValue: string): Uint8Array;
/**
* Sign a canonical string using an Ed25519 private key (PEM or DER).
*
* Signing message: raw UTF-8 bytes of the canonical string.
* NOT sha256(canonical) — signs the data directly.
*
* Returns: standard base64-encoded 64-byte signature.
*/
export declare function signCanonical(canonicalString: string, privateKeyPem: string): string;
/**
* Verify an Ed25519 signature over a canonical string.
*
* @param canonicalString The canonical JSON string that was signed
* @param signatureBase64 Standard base64-encoded signature (64 bytes)
* @param publicKeyPem PEM-encoded Ed25519 public key
*
* Returns true if signature is valid, false otherwise.
* Never throws on invalid signature — only throws on malformed inputs.
*/
export declare function verifyCanonical(canonicalString: string, signatureBase64: string, publicKeyPem: string): boolean;
/**
* Verify using a raw 32-byte public key (from ENS cl.sig.pub).
* Converts to PEM internally then delegates to verifyCanonical.
*/
export declare function verifyCanonicalWithRawKey(canonicalString: string, signatureBase64: string, rawPublicKey: Uint8Array): boolean;
export interface Ed25519KeyPair {
privateKeyPem: string;
publicKeyPem: string;
/** Raw 32-byte public key, ready for ENS cl.sig.pub encoding */
rawPublicKey: Uint8Array;
/** Formatted ENS cl.sig.pub value */
ensPubValue: string;
}
export declare function generateEd25519KeyPair(): Ed25519KeyPair;
//# sourceMappingURL=crypto.d.ts.map
2 changes: 1 addition & 1 deletion dist/crypto.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

146 changes: 126 additions & 20 deletions dist/crypto.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/crypto.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 51 additions & 2 deletions dist/ens.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
import type { EnsResolveOptions, EnsSignerInfo } from './types.js';
export declare function resolveSignerFromENS(options: EnsResolveOptions): Promise<EnsSignerInfo>;
/**
* @commandlayer/runtime-core — ens.ts
*
* ENS text record resolution for CommandLayer signer keys.
*
* ENS record format (production, locked):
* cl.sig.pub = ed25519:<standard_base64_raw32>
* cl.sig.kid = <short key identifier, e.g. vC4WbcNoq2znSCiQ>
* cl.sig.canonical = json.sorted_keys.v1
* cl.receipt.signer = <ens name>
*
* NO hardcoded fallback keys. ENS resolution failure is a hard error.
* If you need test fixtures, use test/fixtures/ens-mock.ts.
*/
export interface EnsSignerRecord {
/** ENS name, e.g. runtime.commandlayer.eth */
name: string;
/** Raw 32-byte Ed25519 public key */
rawPublicKey: Uint8Array;
/** Short key identifier from cl.sig.kid */
kid: string;
/** Canonicalization method from cl.sig.canonical */
canonical: string;
}
/**
* Minimal ENS provider interface.
* Compatible with ethers v6 EnsResolver and any custom resolver.
*/
export interface EnsProvider {
getResolver(name: string): Promise<EnsResolver | null>;
}
export interface EnsResolver {
getText(key: string): Promise<string | null>;
}
/**
* Resolve a CommandLayer signer record from ENS.
*
* Throws on:
* - No resolver found for the ENS name
* - Missing cl.sig.pub record
* - Malformed cl.sig.pub (not ed25519: prefix or wrong key length)
* - cl.sig.canonical mismatch (if present and not json.sorted_keys.v1)
*
* Never falls back to hardcoded keys.
*/
export declare function resolveSignerFromENS(ensName: string, provider: EnsProvider): Promise<EnsSignerRecord>;
/**
* Resolve the public key only (convenience wrapper).
* Use resolveSignerFromENS for full record access.
*/
export declare function resolvePublicKeyFromENS(ensName: string, provider: EnsProvider): Promise<Uint8Array>;
//# sourceMappingURL=ens.d.ts.map
Loading
Loading