@mblaney/holster 1.0.25 → 1.0.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblaney/holster",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/ham.js CHANGED
@@ -95,6 +95,8 @@ Ham.mix = async (change, graph, secure, listen) => {
95
95
 
96
96
  const signedTimestamps = new Set()
97
97
  const signedProperties = new Set()
98
+
99
+ // Verify timestamp signatures (numeric keys in node._["s"])
98
100
  for (const [key, sig] of Object.entries(node._["s"] || {})) {
99
101
  const asNumber = Number(key)
100
102
  if (!isNaN(asNumber) && asNumber > 0) {
@@ -103,7 +105,7 @@ Ham.mix = async (change, graph, secure, listen) => {
103
105
  signedTimestamps.add(asNumber)
104
106
  // Add all properties with this timestamp to signed set
105
107
  for (const [prop, ts] of Object.entries(node._[">"] || {})) {
106
- if (ts === asNumber) {
108
+ if (Number(ts) === asNumber) {
107
109
  signedProperties.add(prop)
108
110
  }
109
111
  }
@@ -111,6 +113,10 @@ Ham.mix = async (change, graph, secure, listen) => {
111
113
  }
112
114
  }
113
115
 
116
+ // Verify per-property signatures (backward compatibility)
117
+ const perPropertySignedProps = await SEA.verifyProperties(node, pub)
118
+ perPropertySignedProps.forEach(prop => signedProperties.add(prop))
119
+
114
120
  // If no properties verified, skip this update
115
121
  if (signedProperties.size === 0) {
116
122
  continue
package/src/sea.js CHANGED
@@ -238,6 +238,59 @@ const SEA = {
238
238
  return false
239
239
  }
240
240
  },
241
+ // Verify individual property signatures stored in node._["s"]
242
+ // Returns array of valid property names
243
+ verifyProperties: async (node, pub, cb) => {
244
+ if (!pub || !node) {
245
+ if (cb) cb([])
246
+ return []
247
+ }
248
+
249
+ const key = await utils.subtle.importKey(
250
+ "jwk",
251
+ utils.jwk(pub),
252
+ {name: "ECDSA", namedCurve: "P-256"},
253
+ false,
254
+ ["verify"],
255
+ )
256
+
257
+ const alg = {name: "ECDSA", hash: {name: "SHA-256"}}
258
+ const valid = []
259
+ const propertySignatures = (node._ && node._["s"]) || {}
260
+
261
+ // Verify each property individually
262
+ for (const k of Object.keys(node).sort()) {
263
+ if (k === "_" || k == userPublicKey) continue
264
+
265
+ const propSig = propertySignatures[k]
266
+ if (!propSig) {
267
+ console.log(`warning: property '${k}' missing signature`)
268
+ continue
269
+ }
270
+
271
+ try {
272
+ const hash = await utils.sha256(node[k])
273
+ const sig = new Uint8Array(SafeBuffer.from(propSig, "base64"))
274
+
275
+ const isValid = await utils.subtle.verify(
276
+ alg,
277
+ key,
278
+ sig,
279
+ new Uint8Array(hash),
280
+ )
281
+ if (isValid) {
282
+ valid.push(k)
283
+ } else {
284
+ console.log(`warning: property '${k}' signature verification failed`)
285
+ }
286
+ } catch (err) {
287
+ console.log(`warning: property '${k}' error verifying: ${err.message}`)
288
+ }
289
+ }
290
+
291
+ if (cb) cb(valid)
292
+ return valid
293
+ },
241
294
  work: async (data, salt, cb) => {
242
295
  if (typeof salt === "function") {
243
296
  cb = salt
package/src/wire.js CHANGED
@@ -302,10 +302,9 @@ const Wire = opt => {
302
302
 
303
303
  const ack = Get(lex, graph)
304
304
  const track = utils.text.random(9)
305
- // Request the whole node in secure mode for verification.
306
305
  const request = JSON.stringify({
307
306
  "#": dup.track(track),
308
- get: opt.secure ? {"#": lex["#"]} : lex,
307
+ get: lex,
309
308
  })
310
309
 
311
310
  if (ack) {