wao 0.29.2 → 0.30.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/esm/hb.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { connect, createSigner } from "@permaweb/aoconnect"
2
2
  import { isEmpty, last, isNotNil, mergeLeft } from "ramda"
3
- import { toAddr, buildTags } from "./utils.js"
4
- import { signer } from "./signer.js"
3
+ import { rsaid, hmacid, toAddr, buildTags } from "./utils.js"
4
+ import { sign, signer } from "./signer.js"
5
5
  import { send as _send } from "./send.js"
6
6
  import hyper_aos from "./lua/hyper-aos.js"
7
7
  import aos_wamr from "./lua/aos_wamr.js"
8
8
  import { from } from "./httpsig.js"
9
9
 
10
10
  const seed = num => {
11
- const array = new Uint8Array(num)
12
- return crypto.getRandomValues(array).toString()
11
+ const array = new Array(num)
12
+ for (let i = 0; i < num; i++) array[i] = Math.floor(Math.random() * 256)
13
+ return Buffer.from(array).toString("base64")
13
14
  }
14
15
 
15
16
  class HB {
@@ -71,8 +72,17 @@ class HB {
71
72
  }
72
73
  if (jwk) this._init(jwk)
73
74
  }
74
-
75
+ async signEncoded(encoded) {
76
+ const { path, ...msg } = encoded
77
+ return await sign({
78
+ jwk: this.jwk,
79
+ msg,
80
+ path,
81
+ url: this.url,
82
+ })
83
+ }
75
84
  _init(jwk) {
85
+ this.jwk = jwk
76
86
  this.signer = createSigner(jwk, this.url)
77
87
  this.addr = toAddr(jwk.n)
78
88
  this.sign = signer({ signer: this.signer, url: this.url })
@@ -108,7 +118,7 @@ class HB {
108
118
 
109
119
  async getLua() {
110
120
  const lua = Buffer.from(hyper_aos, "base64")
111
- const id = await this.cacheModule(lua, "application/lua")
121
+ const id = await this.cacheScript(lua, "application/lua")
112
122
  this.lua ??= id
113
123
  return id
114
124
  }
@@ -132,7 +142,11 @@ class HB {
132
142
  const { slot, pid } = await this.scheduleAOS(args)
133
143
  return { slot, outbox: await this.computeAOS({ pid, slot }) }
134
144
  }
135
-
145
+ async messageLegacy(args) {
146
+ const { slot, pid } = await this.scheduleLegacy(args)
147
+ console.log(slot, pid, args)
148
+ return { slot, res: await this.computeLegacy({ pid, slot }) }
149
+ }
136
150
  path({
137
151
  dev = "message",
138
152
  path,
@@ -214,7 +228,7 @@ class HB {
214
228
  return { res, pid: res.headers.process }
215
229
  }
216
230
 
217
- async cacheModule(data, type) {
231
+ async cacheScript(data, type = "application/lua") {
218
232
  if (!this.cache) {
219
233
  const { pid } = await this.spawn({})
220
234
  this.cache = pid
@@ -261,7 +275,7 @@ class HB {
261
275
  })
262
276
  if (data) _tags.data = data
263
277
  let res = await this.post(_tags)
264
- return { slot: res.headers.slot, res }
278
+ return { slot: res.headers.slot, res, pid }
265
279
  }
266
280
 
267
281
  async spawnAOS(image) {
@@ -368,13 +382,21 @@ class HB {
368
382
  if (isNotNil(from)) params += `&from=${from}`
369
383
  if (isNotNil(to)) params += `&to=${to}`
370
384
  params += `&accept=application/aos-2`
385
+ const {
386
+ out: { body },
387
+ } = await this.get({
388
+ path: "/~scheduler@1.0/schedule",
389
+ target: pid,
390
+ from,
391
+ accept: "application/aos-2",
392
+ })
371
393
  let res = await fetch(`${this.url}/~scheduler@1.0/schedule?${params}`).then(
372
394
  r => r.json()
373
395
  )
374
396
  if (res.page_info.has_next_page) {
375
397
  res.next = async () => {
376
398
  const from2 = last(res.edges).cursor + 1
377
- return await this.message({ pid, from: from2, to, limit })
399
+ return await this.messages({ pid, from: from2, to, limit })
378
400
  }
379
401
  }
380
402
  return res
@@ -433,7 +455,26 @@ class HB {
433
455
  })
434
456
  return JSON.parse(res.body)
435
457
  }
458
+ async commit(obj, opts) {
459
+ const msg = await this.sign(obj, opts)
460
+ const hmacId = hmacid(msg.headers)
461
+ const rsaId = rsaid(msg.headers)
462
+ const committer = this.addr
463
+ const meta = { alg: "rsa-pss-sha512", "commitment-device": "httpsig@1.0" }
464
+ const meta2 = { alg: "hmac-sha256", "commitment-device": "httpsig@1.0" }
436
465
 
466
+ const sigs = {
467
+ signature: msg.headers.signature,
468
+ "signature-input": msg.headers["signature-input"],
469
+ }
470
+ return {
471
+ commitments: {
472
+ [rsaId]: { ...meta, committer, ...sigs },
473
+ [hmacId]: { ...meta2, ...sigs },
474
+ },
475
+ ...obj,
476
+ }
477
+ }
437
478
  async post(obj, json) {
438
479
  const _json = json ? "/~json@1.0/serialize" : ""
439
480
  obj.path += _json
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,14 +177,17 @@ 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
  )
182
188
  }
183
189
  _devices = `, preloaded_devices => [${_devs.join(", ")}]`
184
190
  }
185
-
186
191
  const _wallet = `, priv_key_location => <<"${wallet}">>`
187
192
  const _gateway = gateway
188
193
  ? `, gateway => <<"http://localhost:${gateway}">>`
@@ -198,11 +203,16 @@ export default class HyperBEAM {
198
203
  /*
199
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 } } }]`
200
205
  */
201
- const _p4_non_chargable = this.p4_lua
202
- ? `, p4_non_chargable_routes => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
203
- : !this.simplePay
204
- ? ""
205
- : `, 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
+
206
216
  const _operator = this.operator
207
217
  ? `, operator => <<"${this.operator}">>`
208
218
  : ""
@@ -221,12 +231,13 @@ export default class HyperBEAM {
221
231
 
222
232
  const _on = this.p4_lua
223
233
  ? `, on => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
224
- : this.simplePay
234
+ : this.simple_pay
225
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">> } }`
226
236
  : !isNil(this.faff)
227
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">> } }`
228
238
  : ""
229
239
  const start = `hb:start_mainnet(#{ ${_port}${_gateway}${_wallet}${_faff}${_bundler}${_on}${_p4_non_chargable}${_operator}${_spp}${_devices}${_node_processes}}).`
240
+ console.log(start)
230
241
  return start
231
242
  }
232
243
 
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
@@ -211,8 +196,180 @@ 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
+ async 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 = await hmac(keyBytes, messageBytes)
275
+ return uint8ArrayToBase64url(hmacResult)
276
+ }
277
+
278
+ /**
279
+ * Calculate unsigned message ID following the exact Erlang flow
280
+ */
281
+ async 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 = await hmac(keyBytes, messageBytes)
335
+ return uint8ArrayToBase64url(hmacResult)
336
+ }
337
+
338
+ async 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
354
+ const encoder = new TextEncoder()
355
+ const data = encoder.encode(idsLine)
356
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data)
357
+ const hashArray = new Uint8Array(hashBuffer)
358
+
359
+ // Convert to base64url
360
+ const base64 = btoa(String.fromCharCode(...hashArray))
361
+ const base64url = base64
362
+ .replace(/\+/g, "-")
363
+ .replace(/\//g, "_")
364
+ .replace(/=/g, "")
365
+
366
+ return base64url
367
+ }
368
+ }
369
+
214
370
  // Export all functions
215
371
  export {
372
+ id,
216
373
  generateCommitmentId,
217
374
  generateRsaCommitmentId,
218
375
  generateHmacCommitmentId,
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,6 +1,7 @@
1
1
  import { graphql, parse, validate, buildSchema } from "graphql"
2
2
  import sha256 from "fast-sha256"
3
3
  import {
4
+ id,
4
5
  generateCommitmentId,
5
6
  generateRsaCommitmentId,
6
7
  generateHmacCommitmentId,
@@ -770,4 +771,5 @@ export {
770
771
  isJSON,
771
772
  dirname,
772
773
  toAddr,
774
+ id,
773
775
  }
@@ -8,6 +8,6 @@
8
8
  "deploy": "node scripts/deploy.js"
9
9
  },
10
10
  "dependencies": {
11
- "wao": "^0.28.0"
11
+ "wao": "^0.29.2"
12
12
  }
13
13
  }
@@ -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.2",
3
+ "version": "0.30.0",
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": {