@provenonce/beats-client 0.3.0 → 0.5.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 +2 -0
- package/index.mjs +60 -2
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface BeatAnchor {
|
|
|
64
64
|
difficulty: number;
|
|
65
65
|
epoch: number;
|
|
66
66
|
tx_signature: string;
|
|
67
|
+
/** A-4: Finalized Solana blockhash used as external entropy. Present on v2 anchors. */
|
|
68
|
+
solana_entropy?: string;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
export interface ReceiptEnvelope {
|
package/index.mjs
CHANGED
|
@@ -77,9 +77,34 @@ async function verifyEd25519(payload, signatureBase64, publicKey) {
|
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Recompute an anchor's hash from its fields (B-3).
|
|
80
|
-
*
|
|
81
|
-
*
|
|
80
|
+
* V3: If solana_entropy is present, uses binary-canonical single SHA-256.
|
|
81
|
+
* V1 legacy: string-based hash with difficulty iteration (Node.js only).
|
|
82
82
|
*/
|
|
83
|
+
const ANCHOR_DOMAIN_PREFIX = 'PROVENONCE_BEATS_V1';
|
|
84
|
+
|
|
85
|
+
function hexToUint8Array(hex) {
|
|
86
|
+
const arr = new Uint8Array(hex.length / 2);
|
|
87
|
+
for (let i = 0; i < arr.length; i++) {
|
|
88
|
+
arr[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
89
|
+
}
|
|
90
|
+
return arr;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function u64beBytes(n) {
|
|
94
|
+
const buf = new Uint8Array(8);
|
|
95
|
+
const hi = Math.floor(n / 0x100000000);
|
|
96
|
+
const lo = n >>> 0;
|
|
97
|
+
buf[0] = (hi >>> 24) & 0xff;
|
|
98
|
+
buf[1] = (hi >>> 16) & 0xff;
|
|
99
|
+
buf[2] = (hi >>> 8) & 0xff;
|
|
100
|
+
buf[3] = hi & 0xff;
|
|
101
|
+
buf[4] = (lo >>> 24) & 0xff;
|
|
102
|
+
buf[5] = (lo >>> 16) & 0xff;
|
|
103
|
+
buf[6] = (lo >>> 8) & 0xff;
|
|
104
|
+
buf[7] = lo & 0xff;
|
|
105
|
+
return buf;
|
|
106
|
+
}
|
|
107
|
+
|
|
83
108
|
async function recomputeAnchorHash(anchor) {
|
|
84
109
|
if (!anchor || !HEX64.test(anchor.hash) || !HEX64.test(anchor.prev_hash)) return false;
|
|
85
110
|
if (!Number.isInteger(anchor.beat_index) || anchor.beat_index < 0) return false;
|
|
@@ -87,6 +112,39 @@ async function recomputeAnchorHash(anchor) {
|
|
|
87
112
|
if (!Number.isInteger(anchor.utc) || anchor.utc < 0) return false;
|
|
88
113
|
if (!Number.isInteger(anchor.epoch) || anchor.epoch < 0) return false;
|
|
89
114
|
|
|
115
|
+
if (anchor.solana_entropy) {
|
|
116
|
+
// V3: binary-canonical single SHA-256
|
|
117
|
+
const prefix = new TextEncoder().encode(ANCHOR_DOMAIN_PREFIX); // 19 bytes
|
|
118
|
+
const prev = hexToUint8Array(anchor.prev_hash); // 32 bytes
|
|
119
|
+
const idx = u64beBytes(anchor.beat_index); // 8 bytes
|
|
120
|
+
const entropy = decodeBase58(anchor.solana_entropy); // 32 bytes
|
|
121
|
+
|
|
122
|
+
const preimage = new Uint8Array(prefix.length + prev.length + idx.length + entropy.length);
|
|
123
|
+
preimage.set(prefix, 0);
|
|
124
|
+
preimage.set(prev, prefix.length);
|
|
125
|
+
preimage.set(idx, prefix.length + prev.length);
|
|
126
|
+
preimage.set(entropy, prefix.length + prev.length + idx.length);
|
|
127
|
+
|
|
128
|
+
// Web Crypto path
|
|
129
|
+
const subtle = globalThis.crypto?.subtle;
|
|
130
|
+
if (subtle) {
|
|
131
|
+
try {
|
|
132
|
+
const digest = await subtle.digest('SHA-256', preimage);
|
|
133
|
+
const hashArr = Array.from(new Uint8Array(digest));
|
|
134
|
+
const computed = hashArr.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
135
|
+
return computed === anchor.hash;
|
|
136
|
+
} catch {
|
|
137
|
+
// Fall through to Node.js
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Node.js fallback
|
|
142
|
+
const { createHash } = await import('node:crypto');
|
|
143
|
+
const computed = createHash('sha256').update(preimage).digest('hex');
|
|
144
|
+
return computed === anchor.hash;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// V1 legacy: string-based hash with difficulty iteration
|
|
90
148
|
const { createHash } = await import('node:crypto');
|
|
91
149
|
const nonce = `anchor:${anchor.utc}:${anchor.epoch}`;
|
|
92
150
|
const seed = `${anchor.prev_hash}:${anchor.beat_index}:${nonce}`;
|