wao 0.29.3 → 0.30.2

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/esm/hyperbeam.js CHANGED
@@ -17,8 +17,9 @@ export default class HyperBEAM {
17
17
  c,
18
18
  cmake,
19
19
  faff,
20
- simplePay = false,
21
- simplePayPrice,
20
+ simple_pay = false,
21
+ simple_pay_price,
22
+ p4_non_chargable_routes,
22
23
  p4_lua,
23
24
  store_prefix,
24
25
  operator,
@@ -27,6 +28,7 @@ export default class HyperBEAM {
27
28
  devices,
28
29
  } = {}) {
29
30
  this.devices = devices
31
+ this.p4_non_chargable_routes = p4_non_chargable_routes
30
32
  as ??= shell ? [] : ["genesis_wasm"]
31
33
  this.console = console
32
34
  if (clearCache) {
@@ -46,8 +48,8 @@ export default class HyperBEAM {
46
48
  ? "cache-mainnet-" + Math.floor(Math.random() * 10000000)
47
49
  : "cache-mainnet"
48
50
  this.p4_lua = p4_lua
49
- this.simplePay = simplePay
50
- this.spp = simplePayPrice
51
+ this.simple_pay = simple_pay
52
+ this.spp = simple_pay_price
51
53
  this.operator = operator
52
54
  this.faff = faff
53
55
  this.c = c
@@ -175,7 +177,11 @@ export default class HyperBEAM {
175
177
  if (this.devices) {
176
178
  let _devs = []
177
179
  for (const v of this.devices) {
178
- if (devs[v])
180
+ if (typeof v === "object") {
181
+ _devs.push(
182
+ `#{<<"name">> => <<"${v.name}">>, <<"module">> => ${v.module}}`
183
+ )
184
+ } else if (devs[v])
179
185
  _devs.push(
180
186
  `#{<<"name">> => <<"${devs[v].name}">>, <<"module">> => ${devs[v].module}}`
181
187
  )
@@ -197,11 +203,16 @@ export default class HyperBEAM {
197
203
  /*
198
204
  const _routes = `, routes => [#{ <<"template">> => <<"/result/.*">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${this.cu}">> } }, #{ <<\"template\">> => <<\"/dry-run\">>, <<\"node\">> => #{ <<\"prefix\">> => <<\"http://localhost:${this.cu}\">> } }, #{ <<"template">> => <<"/graphql">>, <<"nodes">> => [#{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => httpc, protocol => http2 } }, #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } }] }, #{ <<"template">> => <<"/raw">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } } }]`
199
205
  */
200
- const _p4_non_chargable = this.p4_lua
201
- ? `, p4_non_chargable_routes => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
202
- : !this.simplePay
203
- ? ""
204
- : `, p4_non_chargable_routes => [#{ <<"template">> => <<"/~simple-pay@1.0/topup">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
206
+ const _p4_non_chargable = this.p4_non_chargable
207
+ ? `, p4_non_chargable_routes => [${this.p4_non_chargable_routes
208
+ .map(() => `#{ <<"template">> => <<"/*~node-process@1.0/*">> }`)
209
+ .join(", ")}]`
210
+ : this.p4_lua
211
+ ? `, p4_non_chargable_routes => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
212
+ : !this.simple_pay
213
+ ? ""
214
+ : `, p4_non_chargable_routes => [#{ <<"template">> => <<"/~simple-pay@1.0/topup">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }, #{ <<"template">> => <<"/~simple-pay@1.0/balance">> }]`
215
+
205
216
  const _operator = this.operator
206
217
  ? `, operator => <<"${this.operator}">>`
207
218
  : ""
@@ -220,7 +231,7 @@ export default class HyperBEAM {
220
231
 
221
232
  const _on = this.p4_lua
222
233
  ? `, on => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
223
- : this.simplePay
234
+ : this.simple_pay
224
235
  ? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> } }`
225
236
  : !isNil(this.faff)
226
237
  ? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> } }`
package/esm/id.js CHANGED
@@ -40,21 +40,6 @@ function base64urlToBase64(str) {
40
40
  return str.replace(/-/g, "+").replace(/_/g, "/")
41
41
  }
42
42
 
43
- /**
44
- * Convert Uint8Array to base64url string
45
- */
46
- function uint8ArrayToBase64url(bytes) {
47
- // Convert to base64
48
- let binary = ""
49
- for (let i = 0; i < bytes.length; i++) {
50
- binary += String.fromCharCode(bytes[i])
51
- }
52
- const base64 = btoa(binary)
53
-
54
- // Convert to base64url
55
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
56
- }
57
-
58
43
  /**
59
44
  * Generate commitment ID for RSA-PSS and ECDSA signatures
60
45
  * The ID is the SHA256 hash of the raw signature bytes
@@ -62,7 +47,7 @@ function uint8ArrayToBase64url(bytes) {
62
47
  * @param {Object} commitment - The commitment object containing signature
63
48
  * @returns {string} The commitment ID in base64url format
64
49
  */
65
- function generateRsaCommitmentId(commitment) {
50
+ function rsaid(commitment) {
66
51
  // Extract the base64 signature from structured field format
67
52
  // Format: "signature-name=:BASE64_SIGNATURE:"
68
53
  const match = commitment.signature.match(/^[^=]+=:([^:]+):/)
@@ -93,7 +78,7 @@ function generateRsaCommitmentId(commitment) {
93
78
  * @param {Object} message - The message with signature and signature-input
94
79
  * @returns {string} The commitment ID in base64url format
95
80
  */
96
- function generateHmacCommitmentId(message) {
81
+ function hmacid(message) {
97
82
  // Parse signature-input to get components
98
83
  const parsed = parseStructuredFieldDictionary(message["signature-input"])
99
84
  if (!parsed || !parsed.components) {
@@ -156,13 +141,13 @@ function generateCommitmentId(commitment, fullMessage = null) {
156
141
  switch (commitment.alg) {
157
142
  case "rsa-pss-sha512":
158
143
  case "ecdsa-p256-sha256":
159
- return generateRsaCommitmentId(commitment)
144
+ return rsaid(commitment)
160
145
 
161
146
  case "hmac-sha256":
162
147
  if (!fullMessage) {
163
148
  throw new Error("HMAC commitment IDs require full message context")
164
149
  }
165
- return generateHmacCommitmentId(fullMessage)
150
+ return hmacid(fullMessage)
166
151
 
167
152
  default:
168
153
  throw new Error(`Unsupported algorithm: ${commitment.alg}`)
@@ -211,12 +196,264 @@ function verifyCommitmentId(commitment, expectedId, fullMessage = null) {
211
196
  }
212
197
  }
213
198
 
199
+ /**
200
+ * Convert Uint8Array to base64url string
201
+ */
202
+ function uint8ArrayToBase64url(bytes) {
203
+ let binary = ""
204
+ for (let i = 0; i < bytes.length; i++) {
205
+ binary += String.fromCharCode(bytes[i])
206
+ }
207
+ const base64 = btoa(binary)
208
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
209
+ }
210
+
211
+ /**
212
+ * Parse structured field dictionary to extract components
213
+ * Handles format: name=(components);params
214
+ */
215
+ function parseSignatureInput(sigInput) {
216
+ // Extract components from format: name=(components);params
217
+ const match = sigInput.match(/[^=]+=\(([^)]+)\)/)
218
+ if (!match) return []
219
+
220
+ // Split components and clean quotes
221
+ return match[1].split(" ").map(c => c.replace(/"/g, ""))
222
+ }
223
+
224
+ /**
225
+ * Calculate HMAC commitment ID for HyperBEAM messages
226
+ */
227
+ function calculateHmacId(message) {
228
+ if (!message["signature-input"]) {
229
+ throw new Error("HMAC calculation requires signature-input")
230
+ }
231
+
232
+ // Parse components from signature-input
233
+ const components = parseSignatureInput(message["signature-input"])
234
+
235
+ // Sort components AS-IS (with @ prefix)
236
+ const sortedComponents = [...components].sort()
237
+
238
+ // Build signature base in sorted order
239
+ const lines = []
240
+
241
+ for (const component of sortedComponents) {
242
+ let fieldName = component
243
+ let value
244
+
245
+ // For derived components (starting with @), remove @ in the signature base
246
+ if (component.startsWith("@")) {
247
+ fieldName = component.substring(1)
248
+ value = message[fieldName]
249
+ } else {
250
+ value = message[component]
251
+ }
252
+
253
+ if (value === undefined || value === null) {
254
+ value = ""
255
+ } else if (typeof value === "number") {
256
+ value = value.toString()
257
+ }
258
+
259
+ lines.push(`"${fieldName}": ${value}`)
260
+ }
261
+
262
+ // Add signature-params line with sorted components (keeping @ prefix)
263
+ const paramsComponents = sortedComponents.join(" ")
264
+ lines.push(
265
+ `"@signature-params": (${paramsComponents});alg="hmac-sha256";keyid="ao"`
266
+ )
267
+
268
+ const signatureBase = lines.join("\n")
269
+
270
+ // Generate HMAC with key "ao"
271
+ const messageBytes = new TextEncoder().encode(signatureBase)
272
+ const keyBytes = new TextEncoder().encode("ao")
273
+
274
+ const hmacResult = hmac(keyBytes, messageBytes)
275
+ return uint8ArrayToBase64url(hmacResult)
276
+ }
277
+
278
+ /**
279
+ * Calculate unsigned message ID following the exact Erlang flow
280
+ */
281
+ function calculateUnsignedId(message) {
282
+ // Derived components from Erlang ?DERIVED_COMPONENTS
283
+ const DERIVED_COMPONENTS = [
284
+ "method",
285
+ "target-uri",
286
+ "authority",
287
+ "scheme",
288
+ "request-target",
289
+ "path",
290
+ "query",
291
+ "query-param",
292
+ "status",
293
+ ]
294
+
295
+ // Convert message for httpsig format
296
+ const httpsigMsg = {}
297
+ for (const [key, value] of Object.entries(message)) {
298
+ httpsigMsg[key.toLowerCase()] = value
299
+ }
300
+
301
+ // Get keys and add @ to derived components
302
+ const keys = Object.keys(httpsigMsg)
303
+ const componentsWithPrefix = keys
304
+ .map(key => {
305
+ // Check if this is a derived component
306
+ if (DERIVED_COMPONENTS.includes(key.replace(/_/g, "-"))) {
307
+ return "@" + key
308
+ }
309
+ return key
310
+ })
311
+ .sort() // Sort AFTER adding @ prefix
312
+
313
+ // Build signature base - use the components in order
314
+ const lines = []
315
+ for (const component of componentsWithPrefix) {
316
+ const key = component.replace("@", "")
317
+ const value = httpsigMsg[key]
318
+ const valueStr = typeof value === "string" ? value : String(value)
319
+ lines.push(`"${key}": ${valueStr}`)
320
+ }
321
+
322
+ // Add signature-params line with the @ prefixes
323
+ const componentsList = componentsWithPrefix.map(k => `"${k}"`).join(" ")
324
+ lines.push(
325
+ `"@signature-params": (${componentsList});alg="hmac-sha256";keyid="ao"`
326
+ )
327
+
328
+ const signatureBase = lines.join("\n")
329
+
330
+ // HMAC with key "ao"
331
+ const messageBytes = new TextEncoder().encode(signatureBase)
332
+ const keyBytes = new TextEncoder().encode("ao")
333
+
334
+ const hmacResult = hmac(keyBytes, messageBytes)
335
+ return uint8ArrayToBase64url(hmacResult)
336
+ }
337
+
338
+ function id(message) {
339
+ // Get commitment IDs
340
+ const commitmentIds = Object.keys(message.commitments || {})
341
+
342
+ if (commitmentIds.length === 0) {
343
+ // No commitments - calculate unsigned ID using HMAC
344
+ return calculateUnsignedId(message)
345
+ } else if (commitmentIds.length === 1) {
346
+ // Single commitment - the ID is just the commitment ID
347
+ return commitmentIds[0]
348
+ } else {
349
+ // Multiple commitments - sort, join with ", ", and hash
350
+ const sortedIds = commitmentIds.sort()
351
+ const idsLine = sortedIds.join(", ")
352
+
353
+ // Calculate SHA-256 hash using fast-sha256
354
+ const encoder = new TextEncoder()
355
+ const data = encoder.encode(idsLine)
356
+ const hashArray = hash(data)
357
+
358
+ // Convert to base64url
359
+ return uint8ArrayToBase64url(hashArray)
360
+ }
361
+ }
214
362
  // Export all functions
215
363
  export {
364
+ id,
216
365
  generateCommitmentId,
217
- generateRsaCommitmentId,
218
- generateHmacCommitmentId,
366
+ rsaid,
367
+ hmacid,
219
368
  extractCommitmentIds,
220
369
  verifyCommitmentId,
221
370
  parseStructuredFieldDictionary,
222
371
  }
372
+
373
+ /**
374
+ * Calculate the next base from a hashpath
375
+ * A hashpath has the format: base/request
376
+ * The next base is calculated as: sha256(base + request)
377
+ *
378
+ * @param {string} hashpath - The current hashpath in format "base/request"
379
+ * @returns {string} The next base in base64url format
380
+ */
381
+ function base(hashpath) {
382
+ // Split the hashpath into base and request
383
+ const parts = hashpath.split("/")
384
+ if (parts.length !== 2) {
385
+ throw new Error("Invalid hashpath format. Expected 'base/request'")
386
+ }
387
+
388
+ const [base, request] = parts
389
+
390
+ // Convert base64url to native binary (Uint8Array)
391
+ const baseBinary = base64urlToUint8Array(base)
392
+ const requestBinary = base64urlToUint8Array(request)
393
+
394
+ // Concatenate base and request
395
+ const combined = new Uint8Array(baseBinary.length + requestBinary.length)
396
+ combined.set(baseBinary, 0)
397
+ combined.set(requestBinary, baseBinary.length)
398
+
399
+ // Calculate SHA256 of the combined data
400
+ const nextBaseHash = hash(combined)
401
+
402
+ // Convert to base64url
403
+ return uint8ArrayToBase64url(nextBaseHash)
404
+ }
405
+
406
+ /**
407
+ * Calculate the next hashpath given the current hashpath and a new message
408
+ *
409
+ * @param {string} currentHashpath - The current hashpath (or null for first operation)
410
+ * @param {Object} newMessage - The new message/request
411
+ * @returns {string} The next hashpath in format "nextBase/newMessageId"
412
+ */
413
+ function hashpath(currentHashpath, newMessage) {
414
+ // Calculate the ID of the new message
415
+ const newMessageId = id(newMessage)
416
+
417
+ if (!currentHashpath) {
418
+ // First operation: the hashpath is just the message ID
419
+ // In the Erlang code, the first hashpath is "baseId/requestId"
420
+ // where baseId is the ID of the initial message
421
+ throw new Error(
422
+ "For first operation, provide the base message ID as currentHashpath"
423
+ )
424
+ }
425
+
426
+ // Check if this is the first operation (currentHashpath is just an ID, not a path)
427
+ if (!currentHashpath.includes("/")) {
428
+ // First operation: currentHashpath is the base message ID
429
+ return `${currentHashpath}/${newMessageId}`
430
+ }
431
+
432
+ // Subsequent operations: calculate the next base from current hashpath
433
+ const nextBase = base(currentHashpath)
434
+
435
+ // Return the new hashpath
436
+ return `${nextBase}/${newMessageId}`
437
+ }
438
+
439
+ /**
440
+ * Helper function to convert base64url string to Uint8Array
441
+ */
442
+ function base64urlToUint8Array(base64url) {
443
+ // Convert base64url to base64
444
+ const base64 = base64urlToBase64(base64url)
445
+
446
+ // Decode base64 to binary string
447
+ const binaryString = atob(base64)
448
+
449
+ // Convert binary string to Uint8Array
450
+ const bytes = new Uint8Array(binaryString.length)
451
+ for (let i = 0; i < binaryString.length; i++) {
452
+ bytes[i] = binaryString.charCodeAt(i)
453
+ }
454
+
455
+ return bytes
456
+ }
457
+
458
+ // Export the new functions
459
+ export { base, hashpath }
package/esm/signer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { toHttpSigner } from "./send.js"
2
2
  import { enc } from "./encode.js"
3
3
  import { createSigner } from "@permaweb/aoconnect"
4
+ export { verify } from "./signer-utils.js"
4
5
 
5
6
  const joinUrl = ({ url, path }) => {
6
7
  if (path.startsWith("http://") || path.startsWith("https://")) return path
@@ -50,12 +51,12 @@ async function _sign({
50
51
  )
51
52
 
52
53
  if (_path) signingFields.push("path")
53
-
54
+ /*
54
55
  if (signingFields.length === 0 && !body) {
55
- lowercaseHeaders["content-length"] = "0"
56
- signingFields.push("content-length")
56
+ //lowercaseHeaders["content-length"] = "0"
57
+ //signingFields.push("content-length")
57
58
  }
58
-
59
+ */
59
60
  const signedRequest = await toHttpSigner(signer)({
60
61
  request: { url: _url, method, headers: lowercaseHeaders },
61
62
  fields: signingFields,
package/esm/utils.js CHANGED
@@ -1,12 +1,6 @@
1
1
  import { graphql, parse, validate, buildSchema } from "graphql"
2
2
  import sha256 from "fast-sha256"
3
- import {
4
- generateCommitmentId,
5
- generateRsaCommitmentId,
6
- generateHmacCommitmentId,
7
- verifyCommitmentId,
8
- } from "./id.js"
9
- export { generateRsaCommitmentId as rsaid, generateHmacCommitmentId as hmacid }
3
+ export { id, base, hashpath, rsaid, hmacid } from "./id.js"
10
4
  import {
11
5
  clone,
12
6
  is,
@@ -1,18 +1,21 @@
1
1
  import assert from "assert"
2
2
  import { describe, it, before, after, beforeEach } from "node:test"
3
- import { HyperBEAM, wait, toAddr } from "wao/test"
3
+ import { HyperBEAM, toAddr } from "wao/test"
4
4
  import { HB } from "wao"
5
5
  import { resolve } from "path"
6
6
  import { readFileSync } from "fs"
7
7
 
8
8
  const cwd = "../../HyperBEAM"
9
- const wallet = ".wallet.json"
9
+ const wallet = resolve(process.cwd(), cwd, ".wallet.json")
10
+
11
+ const jwk = JSON.parse(readFileSync(wallet, "utf8"))
12
+ const addr = toAddr(jwk.n)
10
13
 
11
14
  describe("HyperBEAM", function () {
12
15
  let hbeam, hb, jwk
13
16
 
14
17
  before(async () => {
15
- hbeam = await new HyperBEAM({ c: "12", cmake: "3.5", cwd, wallet }).ready()
18
+ hbeam = await new HyperBEAM({ cwd }).ready()
16
19
  jwk = JSON.parse(readFileSync(resolve(process.cwd(), cwd, wallet), "utf8"))
17
20
  })
18
21
 
@@ -21,7 +24,7 @@ describe("HyperBEAM", function () {
21
24
  after(async () => hbeam.kill())
22
25
 
23
26
  it("should run a HyperBEAM node", async () => {
24
- const info = await hb.getJSON({ path: "/~meta@1.0/info" })
25
- assert.equal(info.address, toAddr(jwk.n))
27
+ const { out } = await hb.getJSON({ path: "/~meta@1.0/build" })
28
+ assert.equal(out.node, "HyperBEAM")
26
29
  })
27
30
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wao",
3
- "version": "0.29.3",
3
+ "version": "0.30.2",
4
4
  "bin": {
5
5
  "wao": "./cjs/cli.js",
6
6
  "wao-esm": "./esm/cli.js"
@@ -24,6 +24,10 @@
24
24
  "./web": {
25
25
  "require": "./cjs/web.js",
26
26
  "import": "./esm/web.js"
27
+ },
28
+ "./signer": {
29
+ "require": "./cjs/signer.js",
30
+ "import": "./esm/signer.js"
27
31
  }
28
32
  },
29
33
  "dependencies": {