@@ -70,6 +70,22 @@ function toHex(bytes: Uint8Array): string {
7070 return Array . from ( bytes ) . map ( b => b . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" ) ;
7171}
7272
73+ function hexToBytes ( hex : string ) : Uint8Array {
74+ const bytes = new Uint8Array ( hex . length / 2 ) ;
75+ for ( let i = 0 ; i < bytes . length ; i ++ ) {
76+ bytes [ i ] = parseInt ( hex . substring ( i * 2 , i * 2 + 2 ) , 16 ) ;
77+ }
78+ return bytes ;
79+ }
80+
81+ function parseSecretKey ( key : string | Uint8Array ) : Uint8Array {
82+ if ( key instanceof Uint8Array ) return key ;
83+ if ( typeof key === "string" && / ^ [ 0 - 9 a - f A - F ] { 64 } $ / . test ( key ) ) {
84+ return hexToBytes ( key ) ;
85+ }
86+ throw new FabricError ( "secretKey must be 32-byte Uint8Array or 64-char hex string" , "decode" ) ;
87+ }
88+
7389/**
7490 * Certrelay client for JavaScript/TypeScript.
7591 *
@@ -88,7 +104,19 @@ function toHex(bytes: Uint8Array): string {
88104 * const fabric = new Fabric({ provider: reactNativeProvider({ Veritas, VeritasAnchors, VeritasQueryContext }) });
89105 * ```
90106 */
107+ export type SignSchnorrFn = ( digest : Uint8Array , secretKey : Uint8Array ) => Uint8Array ;
108+
91109export class Fabric {
110+ private static _signSchnorr : SignSchnorrFn | null = null ;
111+
112+ /**
113+ * Register a Schnorr signing function. Called automatically when
114+ * `@spacesprotocol/fabric-web/signing` is imported.
115+ */
116+ static registerSigner ( fn : SignSchnorrFn ) : void {
117+ Fabric . _signSchnorr = fn ;
118+ }
119+
92120 private provider : VeritasProvider ;
93121 private pool = new RelayPool ( ) ;
94122 private veritas : VeritasHandle | null = null ;
@@ -234,21 +262,35 @@ export class Fabric {
234262 /**
235263 * Build and sign a message ready for broadcasting.
236264 *
265+ * Requires the signing module to be loaded first:
266+ * ```ts
267+ * import "@spacesprotocol/fabric-web/signing";
268+ * ```
269+ *
237270 * @param opts.cert - Certificate bytes (.spacecert)
238- * @param opts.records - RecordSet bytes (from RecordSet.pack().toBytes())
239- * @param opts.sign - Signs a 32-byte digest, returns 64-byte Schnorr signature
271+ * @param opts.records - RecordSet or raw bytes
272+ * @param opts.secretKey - 32-byte secret key as Uint8Array or 64-char hex string
240273 * @param opts.primary - Set SIG_PRIMARY_ZONE flag for num id reverse mapping (default: true)
241274 * @returns Signed message bytes ready for broadcast()
242275 */
243276 async sign ( opts : {
244277 cert : Uint8Array ;
245- records : Uint8Array ;
246- sign : ( signingId : Uint8Array ) => Uint8Array | Promise < Uint8Array > ;
278+ records : Uint8Array | { toBytes ( ) : Uint8Array } ;
279+ secretKey : string | Uint8Array ;
247280 primary ?: boolean ;
248281 } ) : Promise < Uint8Array > {
282+ if ( ! Fabric . _signSchnorr ) {
283+ throw new FabricError (
284+ "signing module not loaded. Import '@spacesprotocol/fabric-web/signing' first." ,
285+ "decode" ,
286+ ) ;
287+ }
249288 await this . bootstrap ( ) ;
250289
251- const { cert, records, sign, primary = true } = opts ;
290+ const { cert, primary = true } = opts ;
291+ const records = opts . records instanceof Uint8Array ? opts . records : opts . records . toBytes ( ) ;
292+ const key = parseSecretKey ( opts . secretKey ) ;
293+ const signFn = Fabric . _signSchnorr ;
252294
253295 const builder = this . provider . createMessageBuilder ( ) ;
254296 builder . addHandle ( cert , records ) ;
@@ -263,7 +305,7 @@ export class Fabric {
263305 if ( primary ) {
264306 u . setFlags ( u . flags ( ) | 0x01 ) ; // SIG_PRIMARY_ZONE
265307 }
266- const sig = await sign ( u . signingId ( ) ) ;
308+ const sig = signFn ( u . signingId ( ) , key ) ;
267309 const signed = u . packSig ( sig ) ;
268310 message . setRecords ( u . canonical ( ) , signed ) ;
269311 }
@@ -274,15 +316,20 @@ export class Fabric {
274316 /**
275317 * Build, sign, and broadcast a message.
276318 *
319+ * Requires the signing module to be loaded first:
320+ * ```ts
321+ * import "@spacesprotocol/fabric-web/signing";
322+ * ```
323+ *
277324 * @param opts.cert - Certificate bytes (.spacecert)
278- * @param opts.records - RecordSet bytes (from RecordSet.pack().toBytes())
279- * @param opts.sign - Signs a 32-byte digest, returns 64-byte Schnorr signature
325+ * @param opts.records - RecordSet or raw bytes
326+ * @param opts.secretKey - 32-byte secret key as Uint8Array or 64-char hex string
280327 * @param opts.primary - Set SIG_PRIMARY_ZONE flag for num id reverse mapping (default: true)
281328 */
282329 async publish ( opts : {
283330 cert : Uint8Array ;
284- records : Uint8Array ;
285- sign : ( signingId : Uint8Array ) => Uint8Array | Promise < Uint8Array > ;
331+ records : Uint8Array | { toBytes ( ) : Uint8Array } ;
332+ secretKey : string | Uint8Array ;
286333 primary ?: boolean ;
287334 } ) : Promise < void > {
288335 const msg = await this . sign ( opts ) ;
0 commit comments