55import { createRequire } from "node:module" ;
66const require = createRequire ( import . meta. url ) ;
77
8- const {
9- canonicalizeStableJsonV1,
10- sha256HexUtf8,
11- parseEd25519Pubkey,
12- verifyEd25519SignatureOverUtf8HashString,
13- recomputeReceiptHashSha256,
14- verifyReceipt,
15- CommandLayerError,
16- CommandLayerClient,
17- } = require ( "../dist/index.cjs" ) ;
18-
8+ const ethers = require ( "ethers" ) ;
199const nacl = require ( "tweetnacl" ) ;
2010
2111let passed = 0 ;
@@ -42,6 +32,64 @@ function assertThrows(fn, name) {
4232 }
4333}
4434
35+ async function assertRejects ( fn , expected , name ) {
36+ try {
37+ await fn ( ) ;
38+ failed ++ ;
39+ console . error ( `FAIL: ${ name } (did not throw)` ) ;
40+ } catch ( err ) {
41+ const msg = err ?. message || String ( err ) ;
42+ if ( ! msg . includes ( expected ) ) {
43+ failed ++ ;
44+ console . error ( `FAIL: ${ name } (unexpected message: ${ msg } )` ) ;
45+ return ;
46+ }
47+ passed ++ ;
48+ console . log ( `PASS: ${ name } ` ) ;
49+ }
50+ }
51+
52+ const kp = nacl . sign . keyPair ( ) ;
53+ const b64Key = Buffer . from ( kp . publicKey ) . toString ( "base64" ) ;
54+ const hexKey = Buffer . from ( kp . publicKey ) . toString ( "hex" ) ;
55+
56+ const ensFixtures = {
57+ "summarizeagent.eth" : { "cl.receipt.signer" : "runtime.commandlayer.eth" } ,
58+ "runtime.commandlayer.eth" : { "cl.sig.pub" : `ed25519:${ b64Key } ` , "cl.sig.kid" : "2026-01" } ,
59+ "missing-signer.eth" : { } ,
60+ "missing-pub.eth" : { "cl.receipt.signer" : "signer-without-pub.eth" } ,
61+ "signer-without-pub.eth" : { "cl.sig.kid" : "2026-01" } ,
62+ "malformed-pub.eth" : { "cl.receipt.signer" : "signer-with-malformed-pub.eth" } ,
63+ "signer-with-malformed-pub.eth" : { "cl.sig.pub" : "ed25519:not-base64" , "cl.sig.kid" : "2026-01" } ,
64+ } ;
65+
66+ class MockResolver {
67+ constructor ( name ) {
68+ this . name = name ;
69+ }
70+
71+ async getText ( key ) {
72+ return ensFixtures [ this . name ] ?. [ key ] ?? "" ;
73+ }
74+ }
75+
76+ ethers . ethers . JsonRpcProvider . prototype . getResolver = async function ( name ) {
77+ if ( ! ( name in ensFixtures ) ) return null ;
78+ return new MockResolver ( name ) ;
79+ } ;
80+
81+ const {
82+ canonicalizeStableJsonV1,
83+ sha256HexUtf8,
84+ parseEd25519Pubkey,
85+ verifyEd25519SignatureOverUtf8HashString,
86+ recomputeReceiptHashSha256,
87+ verifyReceipt,
88+ resolveSignerKey,
89+ CommandLayerError,
90+ CommandLayerClient,
91+ } = require ( "../dist/index.cjs" ) ;
92+
4593// ---- Canonicalization ----
4694
4795assert ( canonicalizeStableJsonV1 ( null ) === "null" , "canonicalize null" ) ;
@@ -84,10 +132,6 @@ assert(sha256HexUtf8("hello") !== sha256HexUtf8("world"), "sha256 differs for di
84132
85133// ---- Ed25519 pubkey parsing ----
86134
87- const kp = nacl . sign . keyPair ( ) ;
88- const b64Key = Buffer . from ( kp . publicKey ) . toString ( "base64" ) ;
89- const hexKey = Buffer . from ( kp . publicKey ) . toString ( "hex" ) ;
90-
91135const pk1 = parseEd25519Pubkey ( b64Key ) ;
92136assert ( pk1 . length === 32 , "parse base64 pubkey" ) ;
93137
@@ -123,6 +167,31 @@ assert(
123167 "wrong key rejects"
124168) ;
125169
170+ // ---- ENS signer key resolution ----
171+
172+ const signerKey = await resolveSignerKey ( "summarizeagent.eth" , "http://mock-rpc.local" ) ;
173+ assert ( signerKey . algorithm === "ed25519" , "resolveSignerKey returns algorithm" ) ;
174+ assert ( signerKey . kid === "2026-01" , "resolveSignerKey returns kid from cl.sig.kid" ) ;
175+ assert ( Buffer . from ( signerKey . rawPublicKeyBytes ) . toString ( "base64" ) === b64Key , "resolveSignerKey returns public key bytes from cl.sig.pub" ) ;
176+
177+ await assertRejects (
178+ ( ) => resolveSignerKey ( "missing-signer.eth" , "http://mock-rpc.local" ) ,
179+ "ENS TXT cl.receipt.signer missing" ,
180+ "resolveSignerKey throws clear error when cl.receipt.signer missing"
181+ ) ;
182+
183+ await assertRejects (
184+ ( ) => resolveSignerKey ( "missing-pub.eth" , "http://mock-rpc.local" ) ,
185+ "ENS TXT cl.sig.pub missing" ,
186+ "resolveSignerKey throws clear error when cl.sig.pub missing"
187+ ) ;
188+
189+ await assertRejects (
190+ ( ) => resolveSignerKey ( "malformed-pub.eth" , "http://mock-rpc.local" ) ,
191+ "ENS TXT cl.sig.pub malformed" ,
192+ "resolveSignerKey throws clear error when cl.sig.pub malformed"
193+ ) ;
194+
126195// ---- Receipt verification (end-to-end) ----
127196
128197const receipt = {
@@ -147,11 +216,20 @@ receipt.metadata.proof.signature_b64 = Buffer.from(receiptSig).toString("base64"
147216receipt . metadata . receipt_id = hash_sha256 ;
148217
149218const vr = await verifyReceipt ( receipt , { publicKey : `ed25519:${ b64Key } ` } ) ;
150- assert ( vr . ok === true , "verifyReceipt ok for valid receipt" ) ;
219+ assert ( vr . ok === true , "verifyReceipt ok for valid receipt (explicit key) " ) ;
151220assert ( vr . checks . hash_matches === true , "verifyReceipt hash matches" ) ;
152221assert ( vr . checks . signature_valid === true , "verifyReceipt signature valid" ) ;
153222assert ( vr . checks . receipt_id_matches === true , "verifyReceipt receipt_id matches" ) ;
154223
224+ const vrEns = await verifyReceipt ( receipt , {
225+ ens : {
226+ name : "summarizeagent.eth" ,
227+ rpcUrl : "http://mock-rpc.local"
228+ }
229+ } ) ;
230+ assert ( vrEns . ok === true , "verifyReceipt ok with ENS cl.receipt.signer + cl.sig.pub" ) ;
231+ assert ( vrEns . values . pubkey_source === "ens" , "verifyReceipt reports ENS key source" ) ;
232+
155233// Tampered receipt
156234const tamperedReceipt = JSON . parse ( JSON . stringify ( receipt ) ) ;
157235tamperedReceipt . result . summary = "tampered" ;
0 commit comments