@dopamint-fun/open-sdk 0.2.0-dev.2 → 0.2.0-dev.4

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/dist/agentHttp.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * server reconstructs them from the request it actually received, so there is
14
14
  * no restated copy to compare and therefore no comparison to forget.
15
15
  */
16
- import { ByteWriter, frameSigningBytes, fromHex, textBytes, toHex } from "./bytes.js";
16
+ import { ByteReader, ByteWriter, frameSigningBytes, fromHex, textBytes, toHex, } from "./bytes.js";
17
17
  import { blake2b256 } from "./crypto.js";
18
18
  import { signRaw } from "./keypair.js";
19
19
  const AGENT_HTTP_CAPABILITY_DOMAIN = textBytes("dopa_open::agent_http_capability::v1");
@@ -94,20 +94,17 @@ export function decodeAgentHttpHeader(value) {
94
94
  const bytes = fromHex(value);
95
95
  if (bytes.length !== HEADER_BYTES)
96
96
  throw new Error(`capability header must be ${HEADER_BYTES} bytes, got ${bytes.length}`);
97
- const readU64 = (offset) => {
98
- let out = 0n;
99
- for (let index = 0; index < 8; index++)
100
- out = (out << 8n) | BigInt(bytes[offset + index]);
101
- return out;
102
- };
103
- return {
104
- agentId: bytes.slice(0, 32),
105
- agentPublicKey: bytes.slice(32, 64),
106
- issuedAtMs: readU64(64),
107
- expiresAtMs: readU64(72),
108
- nonce: bytes.slice(80, 80 + AGENT_HTTP_CAPABILITY_NONCE_BYTES),
109
- signature: bytes.slice(80 + AGENT_HTTP_CAPABILITY_NONCE_BYTES),
97
+ const reader = new ByteReader(bytes);
98
+ const capability = {
99
+ agentId: reader.readFixed(32, "agent id"),
100
+ agentPublicKey: reader.readFixed(32, "agent public key"),
101
+ issuedAtMs: reader.readU64("issued_at_ms"),
102
+ expiresAtMs: reader.readU64("expires_at_ms"),
103
+ nonce: reader.readFixed(AGENT_HTTP_CAPABILITY_NONCE_BYTES, "nonce"),
104
+ signature: reader.readFixed(SIGNATURE_BYTES, "signature"),
110
105
  };
106
+ reader.finish();
107
+ return capability;
111
108
  }
112
109
  /** Mint and sign one capability, and return the header to send with it.
113
110
  *
package/dist/claim.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { signRaw } from "./keypair.js";
3
- import { concatBytes, equalBytes, frameSigningBytes, fromHex, textBytes, toHex0x, } from "./bytes.js";
3
+ import { ByteReader, ByteWriter, equalBytes, frameSigningBytes, fromHex, textBytes, toHex0x, } from "./bytes.js";
4
4
  /* The agent's half of a claim: an invitation its own key signs.
5
5
  *
6
6
  * A wallet claims an agent by signing for it in the arena's UI. That proves
@@ -22,16 +22,6 @@ export const AGENT_CLAIM_INVITE_MAX_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
22
22
  * nonce || signature`, every field fixed-width. */
23
23
  export const AGENT_CLAIM_INVITE_TOKEN_BYTES = 32 + 32 + 32 + 8 + 8 + 16 + 64;
24
24
  const ZERO_OWNER = new Uint8Array(32);
25
- function u64be(value) {
26
- const out = new Uint8Array(8);
27
- new DataView(out.buffer).setBigUint64(0, value, false);
28
- return out;
29
- }
30
- function fixed(bytes, length, what) {
31
- if (bytes.length !== length)
32
- throw new Error(`${what} must be ${length} bytes, got ${bytes.length}`);
33
- return bytes;
34
- }
35
25
  /** `AgentClaimInvite::canonical_payload` -- the framed fields. */
36
26
  export function claimInviteCanonicalPayload(invite) {
37
27
  if (invite.expiresAtMs <= invite.issuedAtMs)
@@ -41,7 +31,18 @@ export function claimInviteCanonicalPayload(invite) {
41
31
  throw new Error("an invitation may stay open for at most a week");
42
32
  if (invite.nonce.every((byte) => byte === 0))
43
33
  throw new Error("the invitation's nonce must not be all zero");
44
- return concatBytes(AGENT_CLAIM_INVITE_DOMAIN, Uint8Array.of(0, CANONICAL_WIRE_VERSION, INVITE_OPERATION), fixed(invite.agentId, 32, "agent id"), invite.owner ? fixed(invite.owner, 32, "owner") : ZERO_OWNER, fixed(invite.agentPublicKey, 32, "agent public key"), u64be(invite.issuedAtMs), u64be(invite.expiresAtMs), fixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce"));
34
+ return new ByteWriter()
35
+ .pushBytes(AGENT_CLAIM_INVITE_DOMAIN)
36
+ .pushByte(0)
37
+ .pushByte(CANONICAL_WIRE_VERSION)
38
+ .pushByte(INVITE_OPERATION)
39
+ .pushFixed(invite.agentId, 32, "agent id")
40
+ .pushFixed(invite.owner ?? ZERO_OWNER, 32, "owner")
41
+ .pushFixed(invite.agentPublicKey, 32, "agent public key")
42
+ .pushU64(invite.issuedAtMs)
43
+ .pushU64(invite.expiresAtMs)
44
+ .pushFixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce")
45
+ .bytes();
45
46
  }
46
47
  /** The bytes the agent's key signs, raw ed25519. */
47
48
  export function claimInviteSigningBytes(invite) {
@@ -66,7 +67,15 @@ export async function mintClaimInvite(agent, agentId, options = {}) {
66
67
  function claimInviteBytes(invite) {
67
68
  if (invite.signature.length !== 64)
68
69
  throw new Error("an invitation is encoded only once it is signed");
69
- return concatBytes(fixed(invite.agentId, 32, "agent id"), invite.owner ?? ZERO_OWNER, fixed(invite.agentPublicKey, 32, "agent public key"), u64be(invite.issuedAtMs), u64be(invite.expiresAtMs), fixed(invite.nonce, 16, "nonce"), invite.signature);
70
+ return new ByteWriter()
71
+ .pushFixed(invite.agentId, 32, "agent id")
72
+ .pushFixed(invite.owner ?? ZERO_OWNER, 32, "owner")
73
+ .pushFixed(invite.agentPublicKey, 32, "agent public key")
74
+ .pushU64(invite.issuedAtMs)
75
+ .pushU64(invite.expiresAtMs)
76
+ .pushFixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce")
77
+ .pushBytes(invite.signature)
78
+ .bytes();
70
79
  }
71
80
  /** The token the link carries and the claim body posts back: 0x-hex.
72
81
  *
@@ -115,16 +124,23 @@ export function decodeClaimInvite(token) {
115
124
  const bytes = claimInviteTokenBytes(token.trim());
116
125
  if (bytes.length !== AGENT_CLAIM_INVITE_TOKEN_BYTES)
117
126
  throw new Error(`an invitation token is ${AGENT_CLAIM_INVITE_TOKEN_BYTES} bytes, got ${bytes.length}`);
118
- const view = new DataView(bytes.buffer, bytes.byteOffset);
119
- const owner = bytes.slice(32, 64);
127
+ const reader = new ByteReader(bytes);
128
+ const agentId = reader.readFixed(32, "agent id");
129
+ const owner = reader.readFixed(32, "owner");
130
+ const agentPublicKey = reader.readFixed(32, "agent public key");
131
+ const issuedAtMs = reader.readU64("issued");
132
+ const expiresAtMs = reader.readU64("expires");
133
+ const nonce = reader.readFixed(AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce");
134
+ const signature = reader.readFixed(64, "signature");
135
+ reader.finish();
120
136
  return {
121
- agentId: bytes.slice(0, 32),
137
+ agentId,
122
138
  owner: equalBytes(owner, ZERO_OWNER) ? null : owner,
123
- agentPublicKey: bytes.slice(64, 96),
124
- issuedAtMs: view.getBigUint64(96, false),
125
- expiresAtMs: view.getBigUint64(104, false),
126
- nonce: bytes.slice(112, 128),
127
- signature: bytes.slice(128),
139
+ agentPublicKey,
140
+ issuedAtMs,
141
+ expiresAtMs,
142
+ nonce,
143
+ signature,
128
144
  };
129
145
  }
130
146
  /** Where the owner goes to accept: the agent's claim page with the token. */
package/dist/identity.js CHANGED
@@ -7,11 +7,13 @@
7
7
  * and this key can no longer sign for it -- which is the point of a claim.
8
8
  *
9
9
  * The payload mirrors `AgentIdentityEdit::canonical_payload` in
10
- * `backend/dopa-open/product/src/domain/custodial/key_rotation.rs`: every
11
- * field is length-framed, so "ab"/"c" and "a"/"bc" cannot sign the same
12
- * bytes. The product parses before it verifies -- it trims each field and
13
- * lower-cases the handle -- so this signs the parsed form, or the signature
14
- * is over bytes the store never holds. */
10
+ * `backend/dopa-open/product/src/domain/custodial/key_rotation.rs`: the two
11
+ * ids are fixed 32 bytes and every named field is length-framed, so "ab"/"c"
12
+ * and "a"/"bc" cannot sign the same bytes, and neither can a shorter agent id
13
+ * borrow a byte from the owner behind it. The product parses before it
14
+ * verifies -- it trims each field and lower-cases the handle -- so this signs
15
+ * the parsed form, or the signature is over bytes the store never holds. */
16
+ import { ByteWriter, fromHex, textBytes } from "./bytes.js";
15
17
  import { signOwnerAuthenticator } from "./keypair.js";
16
18
  import { refusalMessageFromText } from "./refusal.js";
17
19
  const IDENTITY_DOMAIN = "dopa_open::agent_identity::v1";
@@ -25,40 +27,20 @@ export function parseIdentityFields(fields) {
25
27
  bio: fields.bio?.trim() || null,
26
28
  };
27
29
  }
28
- function u64be(value) {
29
- const out = new Uint8Array(8);
30
- new DataView(out.buffer).setBigUint64(0, BigInt(value));
31
- return out;
32
- }
33
- function fromHex(value) {
34
- const hex = value.replace(/^0x/i, "");
35
- const out = new Uint8Array(hex.length / 2);
36
- for (let index = 0; index < out.length; index++)
37
- out[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
38
- return out;
39
- }
40
30
  /** The exact bytes the owning address signs to name an agent. */
41
31
  export function canonicalIdentityPayload(edit) {
42
- const encoder = new TextEncoder();
43
- const domain = encoder.encode(IDENTITY_DOMAIN);
32
+ const domain = textBytes(IDENTITY_DOMAIN);
44
33
  const parsed = parseIdentityFields(edit.fields);
45
- const parts = [parsed.name ?? "", parsed.handle ?? "", parsed.bio ?? ""].map((field) => encoder.encode(field));
46
- const chunks = [
47
- u64be(domain.length),
48
- domain,
49
- fromHex(edit.agentId),
50
- fromHex(edit.owner),
51
- ...parts.flatMap((field) => [u64be(field.length), field]),
52
- u64be(edit.issuedAtMs),
53
- u64be(edit.expiresAtMs),
54
- ];
55
- const out = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
56
- let offset = 0;
57
- for (const chunk of chunks) {
58
- out.set(chunk, offset);
59
- offset += chunk.length;
34
+ const writer = new ByteWriter()
35
+ .pushU64(domain.length)
36
+ .pushBytes(domain)
37
+ .pushFixed(fromHex(edit.agentId), 32, "agent id")
38
+ .pushFixed(fromHex(edit.owner), 32, "owner");
39
+ for (const field of [parsed.name ?? "", parsed.handle ?? "", parsed.bio ?? ""]) {
40
+ const bytes = textBytes(field);
41
+ writer.pushU64(bytes.length).pushBytes(bytes);
60
42
  }
61
- return out;
43
+ return writer.pushU64(edit.issuedAtMs).pushU64(edit.expiresAtMs).bytes();
62
44
  }
63
45
  /** Name an agent, as the address that owns it. Answers what the arena stored. */
64
46
  export async function nameAgent(args) {
@@ -73,7 +55,7 @@ export async function nameAgent(args) {
73
55
  issuedAtMs,
74
56
  expiresAtMs,
75
57
  }));
76
- const agentId = `0x${args.agentId.replace(/^0x/i, "")}`;
58
+ const agentId = `0x${args.agentId.replace(/^0x/, "")}`;
77
59
  const response = await fetchImpl(`${args.productUrl.replace(/\/$/, "")}/open/v1/agents/${agentId}/identity`, {
78
60
  method: "PUT",
79
61
  headers: { "content-type": "application/json" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dopamint-fun/open-sdk",
3
- "version": "0.2.0-dev.2",
3
+ "version": "0.2.0-dev.4",
4
4
  "description": "DOPA-OPEN client SDK: generate and hold your own agent key, sign registrations, and play a Participant Session",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {