@provenonce/beats-client 0.5.0 → 1.0.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/index.d.ts +118 -1
- package/index.mjs +136 -0
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -117,10 +117,78 @@ export interface VerifyApiResponse {
|
|
|
117
117
|
[key: string]: unknown;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export interface BeatObject {
|
|
121
|
+
index: number;
|
|
122
|
+
hash: string;
|
|
123
|
+
prev: string;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
nonce?: string;
|
|
126
|
+
anchor_hash?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface SpotCheck {
|
|
130
|
+
index: number;
|
|
131
|
+
hash: string;
|
|
132
|
+
prev: string;
|
|
133
|
+
nonce?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface WorkProofRequest {
|
|
137
|
+
from_beat: number;
|
|
138
|
+
to_beat: number;
|
|
139
|
+
from_hash: string;
|
|
140
|
+
to_hash: string;
|
|
141
|
+
beats_computed: number;
|
|
142
|
+
difficulty: number;
|
|
143
|
+
anchor_index: number;
|
|
144
|
+
anchor_hash?: string;
|
|
145
|
+
spot_checks: SpotCheck[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface WorkProofReceiptPayload {
|
|
149
|
+
type: 'work_proof';
|
|
150
|
+
beats_verified: number;
|
|
151
|
+
difficulty: number;
|
|
152
|
+
anchor_index: number;
|
|
153
|
+
anchor_hash: string | null;
|
|
154
|
+
utc: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface WorkProofResponse {
|
|
158
|
+
ok: boolean;
|
|
159
|
+
valid: boolean;
|
|
160
|
+
receipt?: WorkProofReceiptPayload;
|
|
161
|
+
signature?: string;
|
|
162
|
+
public_key?: string;
|
|
163
|
+
spot_checks_verified?: number;
|
|
164
|
+
reason?: string;
|
|
165
|
+
failed_indices?: number[];
|
|
166
|
+
_note?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface KeyInfo {
|
|
170
|
+
public_key_base58: string;
|
|
171
|
+
public_key_hex: string;
|
|
172
|
+
signing_context: string;
|
|
173
|
+
purpose: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface KeyResponse {
|
|
177
|
+
/** Timestamp receipt key (backward compat) */
|
|
178
|
+
public_key_base58: string;
|
|
179
|
+
public_key_hex: string;
|
|
180
|
+
algorithm: string;
|
|
181
|
+
keys: {
|
|
182
|
+
timestamp: KeyInfo;
|
|
183
|
+
work_proof: KeyInfo;
|
|
184
|
+
};
|
|
185
|
+
_note?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
120
188
|
export interface BeatsClient {
|
|
121
189
|
getHealth(): Promise<HealthResponse>;
|
|
122
190
|
getAnchor(opts?: AnchorOptions): Promise<AnchorResponse>;
|
|
123
|
-
getKey(): Promise<
|
|
191
|
+
getKey(): Promise<KeyResponse>;
|
|
124
192
|
verify(payload: unknown): Promise<VerifyApiResponse>;
|
|
125
193
|
timestampHash(hash: string): Promise<TimestampResponse>;
|
|
126
194
|
|
|
@@ -143,8 +211,57 @@ export interface BeatsClient {
|
|
|
143
211
|
/** Returns true if chain continuity is broken and resync() is required. */
|
|
144
212
|
isBroken(): boolean;
|
|
145
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Submit a work proof to the Beats service and receive a signed receipt.
|
|
216
|
+
* The receipt certifies N beats at difficulty D anchored to a global beat.
|
|
217
|
+
* Policy-free: the caller (Registry or any consumer) decides what N means.
|
|
218
|
+
*/
|
|
219
|
+
submitWorkProof(proof: WorkProofRequest): Promise<WorkProofResponse>;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Verify a work-proof receipt signature offline.
|
|
223
|
+
* Uses the work_proof key from GET /api/v1/beat/key (distinct from timestamp key).
|
|
224
|
+
*/
|
|
225
|
+
verifyWorkProofReceipt(
|
|
226
|
+
receiptResponse: WorkProofResponse,
|
|
227
|
+
opts?: { publicKey?: string },
|
|
228
|
+
): Promise<boolean>;
|
|
229
|
+
|
|
146
230
|
/** Internal: resolve public key from cache or auto-fetch. */
|
|
147
231
|
_resolveKey(): Promise<string | null>;
|
|
148
232
|
}
|
|
149
233
|
|
|
150
234
|
export declare function createBeatsClient(options?: BeatsClientOptions): BeatsClient;
|
|
235
|
+
|
|
236
|
+
// ============ STANDALONE COMPUTE (Node.js only) ============
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Compute a single beat — sequential SHA-256 hash chain.
|
|
240
|
+
* Node.js only (uses node:crypto). Not browser-compatible.
|
|
241
|
+
*
|
|
242
|
+
* @param prevHash Previous beat hash (64 hex)
|
|
243
|
+
* @param beatIndex Beat index (monotonically increasing)
|
|
244
|
+
* @param difficulty Hash iterations per beat (default 1000)
|
|
245
|
+
* @param nonce Optional entropy
|
|
246
|
+
* @param anchorHash Optional global anchor hash to weave in
|
|
247
|
+
*/
|
|
248
|
+
export declare function computeBeat(
|
|
249
|
+
prevHash: string,
|
|
250
|
+
beatIndex: number,
|
|
251
|
+
difficulty?: number,
|
|
252
|
+
nonce?: string,
|
|
253
|
+
anchorHash?: string,
|
|
254
|
+
): Promise<BeatObject>;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Compute the genesis beat for a local chain.
|
|
258
|
+
* Deterministic from caller-provided seed + domain prefix.
|
|
259
|
+
* Node.js only.
|
|
260
|
+
*
|
|
261
|
+
* @param seed Unique identifier (e.g. agent hash)
|
|
262
|
+
* @param domainPrefix Namespace prefix (default: 'beats:genesis:v1:')
|
|
263
|
+
*/
|
|
264
|
+
export declare function createGenesisBeat(
|
|
265
|
+
seed: string,
|
|
266
|
+
domainPrefix?: string,
|
|
267
|
+
): Promise<BeatObject>;
|
package/index.mjs
CHANGED
|
@@ -530,5 +530,141 @@ export function createBeatsClient({
|
|
|
530
530
|
return null;
|
|
531
531
|
}
|
|
532
532
|
},
|
|
533
|
+
|
|
534
|
+
// ---- Work Proof ----
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Submit a work proof to the Beats service and receive a signed receipt.
|
|
538
|
+
*
|
|
539
|
+
* @param {object} proof
|
|
540
|
+
* @param {number} proof.from_beat Starting beat index
|
|
541
|
+
* @param {number} proof.to_beat Ending beat index
|
|
542
|
+
* @param {string} proof.from_hash Hash at from_beat (64 hex)
|
|
543
|
+
* @param {string} proof.to_hash Hash at to_beat (64 hex)
|
|
544
|
+
* @param {number} proof.beats_computed to_beat - from_beat
|
|
545
|
+
* @param {number} proof.difficulty Hash iterations per beat
|
|
546
|
+
* @param {number} proof.anchor_index Global anchor index woven in
|
|
547
|
+
* @param {string} [proof.anchor_hash] Anchor hash woven in (64 hex)
|
|
548
|
+
* @param {Array} proof.spot_checks Spot-checked beats for verification
|
|
549
|
+
* @param {number} .index Beat index
|
|
550
|
+
* @param {string} .hash Hash at this beat (64 hex)
|
|
551
|
+
* @param {string} .prev Previous hash (64 hex)
|
|
552
|
+
* @param {string} [.nonce] Optional nonce
|
|
553
|
+
*
|
|
554
|
+
* @returns {Promise<WorkProofResponse>}
|
|
555
|
+
*/
|
|
556
|
+
submitWorkProof(proof) {
|
|
557
|
+
return request('/api/v1/beat/work-proof', {
|
|
558
|
+
method: 'POST',
|
|
559
|
+
headers: { 'content-type': 'application/json' },
|
|
560
|
+
body: JSON.stringify(proof),
|
|
561
|
+
});
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Verify a work-proof receipt signature (offline).
|
|
566
|
+
* Uses the work_proof key from GET /api/v1/beat/key, not the timestamp key.
|
|
567
|
+
*
|
|
568
|
+
* @param {object} receiptResponse The full response from submitWorkProof()
|
|
569
|
+
* @param {object} [opts]
|
|
570
|
+
* @param {string} [opts.publicKey] Override: hex or base58 work-proof public key
|
|
571
|
+
*/
|
|
572
|
+
async verifyWorkProofReceipt(receiptResponse, opts = {}) {
|
|
573
|
+
const payload = receiptResponse?.receipt;
|
|
574
|
+
const signature = receiptResponse?.signature;
|
|
575
|
+
if (!payload || !signature) return false;
|
|
576
|
+
|
|
577
|
+
// Resolve work-proof public key: param > cached from /key endpoint
|
|
578
|
+
let key = opts.publicKey;
|
|
579
|
+
if (!key) {
|
|
580
|
+
try {
|
|
581
|
+
const keyData = await this.getKey();
|
|
582
|
+
key = keyData?.keys?.work_proof?.public_key_hex ||
|
|
583
|
+
keyData?.keys?.work_proof?.public_key_base58;
|
|
584
|
+
} catch {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (!key) return false;
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
return await verifyEd25519(payload, signature, key);
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ============ STANDALONE COMPUTE (Node.js only) ============
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Compute a single beat — sequential SHA-256 hash chain.
|
|
603
|
+
*
|
|
604
|
+
* Each beat requires `difficulty` sequential hash iterations.
|
|
605
|
+
* Because SHA-256 output feeds the next input, this cannot be
|
|
606
|
+
* parallelized. This is the CPU-work primitive for local beat chains.
|
|
607
|
+
*
|
|
608
|
+
* Node.js only: uses node:crypto. Not available in browser environments.
|
|
609
|
+
*
|
|
610
|
+
* @param {string} prevHash Previous beat hash (64 hex)
|
|
611
|
+
* @param {number} beatIndex Beat index (monotonically increasing)
|
|
612
|
+
* @param {number} [difficulty=1000] Hash iterations per beat
|
|
613
|
+
* @param {string} [nonce] Optional entropy
|
|
614
|
+
* @param {string} [anchorHash] Global anchor hash to weave in
|
|
615
|
+
* @returns {{ index, hash, prev, timestamp, nonce?, anchor_hash? }}
|
|
616
|
+
*/
|
|
617
|
+
export async function computeBeat(prevHash, beatIndex, difficulty = 1000, nonce, anchorHash) {
|
|
618
|
+
if (typeof prevHash !== 'string' || prevHash.length === 0) {
|
|
619
|
+
throw new Error('computeBeat: prevHash must be a non-empty string');
|
|
620
|
+
}
|
|
621
|
+
if (!Number.isInteger(beatIndex) || beatIndex < 0) {
|
|
622
|
+
throw new Error('computeBeat: beatIndex must be a non-negative integer');
|
|
623
|
+
}
|
|
624
|
+
const d = Math.max(1, Math.min(Number.isFinite(difficulty) ? Math.floor(difficulty) : 1000, 1_000_000));
|
|
625
|
+
|
|
626
|
+
const { createHash } = await import('node:crypto');
|
|
627
|
+
|
|
628
|
+
const seed = anchorHash
|
|
629
|
+
? `${prevHash}:${beatIndex}:${nonce || ''}:${anchorHash}`
|
|
630
|
+
: `${prevHash}:${beatIndex}:${nonce || ''}`;
|
|
631
|
+
|
|
632
|
+
let current = createHash('sha256').update(seed, 'utf8').digest('hex');
|
|
633
|
+
for (let i = 0; i < d; i++) {
|
|
634
|
+
current = createHash('sha256').update(current, 'utf8').digest('hex');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
index: beatIndex,
|
|
639
|
+
hash: current,
|
|
640
|
+
prev: prevHash,
|
|
641
|
+
timestamp: Date.now(),
|
|
642
|
+
nonce,
|
|
643
|
+
anchor_hash: anchorHash,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Compute the genesis beat for a local chain.
|
|
649
|
+
* Deterministic from caller-provided seed + optional domain prefix.
|
|
650
|
+
*
|
|
651
|
+
* Node.js only.
|
|
652
|
+
*
|
|
653
|
+
* @param {string} seed Unique identifier for this chain (e.g. agent hash)
|
|
654
|
+
* @param {string} [domainPrefix='beats:genesis:v1:'] Namespace prefix
|
|
655
|
+
*/
|
|
656
|
+
export async function createGenesisBeat(seed, domainPrefix = 'beats:genesis:v1:') {
|
|
657
|
+
if (!seed || typeof seed !== 'string') {
|
|
658
|
+
throw new Error('createGenesisBeat: seed must be a non-empty string');
|
|
659
|
+
}
|
|
660
|
+
const { createHash } = await import('node:crypto');
|
|
661
|
+
const genesisHash = createHash('sha256')
|
|
662
|
+
.update(`${domainPrefix}${seed}`, 'utf8')
|
|
663
|
+
.digest('hex');
|
|
664
|
+
return {
|
|
665
|
+
index: 0,
|
|
666
|
+
hash: genesisHash,
|
|
667
|
+
prev: '0'.repeat(64),
|
|
668
|
+
timestamp: Date.now(),
|
|
533
669
|
};
|
|
534
670
|
}
|