@dopamint-fun/open-sdk 0.2.0-dev.10 → 0.2.0-dev.12

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/bytes.d.ts CHANGED
@@ -4,6 +4,15 @@ export declare function toHex0x(bytes: Uint8Array): string;
4
4
  /** Length-aware equality. `every()` on an empty left-hand side is vacuously
5
5
  * true, which would accept a truncated hex decode as a matching key. */
6
6
  export declare function equalBytes(left: Uint8Array, right: Uint8Array): boolean;
7
+ /** One `label:value` line per field, under a line naming the domain.
8
+ *
9
+ * What an owner's wallet is asked to approve is text, not a frame: a Sui
10
+ * wallet renders a personal message as characters, and a framed payload
11
+ * reached a person as NUL bytes and junk they could not check. The order of
12
+ * the fields is signed, and so is the absence of a trailing newline. */
13
+ export declare function signedMessage(domain: string, fields: readonly (readonly [label: string, value: string])[]): Uint8Array;
14
+ /** An id or address at the one width the message renders it, or a throw. */
15
+ export declare function fixedHex0x(value: string, length: number, field: string): string;
7
16
  /** An append-only byte buffer with the wire's own vocabulary. */
8
17
  export declare class ByteWriter {
9
18
  private chunks;
package/dist/bytes.js CHANGED
@@ -37,6 +37,23 @@ export function equalBytes(left, right) {
37
37
  return false;
38
38
  return left.every((byte, index) => byte === right[index]);
39
39
  }
40
+ /** One `label:value` line per field, under a line naming the domain.
41
+ *
42
+ * What an owner's wallet is asked to approve is text, not a frame: a Sui
43
+ * wallet renders a personal message as characters, and a framed payload
44
+ * reached a person as NUL bytes and junk they could not check. The order of
45
+ * the fields is signed, and so is the absence of a trailing newline. */
46
+ export function signedMessage(domain, fields) {
47
+ const lines = fields.map(([label, value]) => `${label}:${value}`);
48
+ return textBytes([domain, ...lines].join("\n"));
49
+ }
50
+ /** An id or address at the one width the message renders it, or a throw. */
51
+ export function fixedHex0x(value, length, field) {
52
+ const bytes = fromHex(value);
53
+ if (bytes.length !== length)
54
+ throw new Error(`${field} must be ${length} bytes, got ${bytes.length}`);
55
+ return toHex0x(bytes);
56
+ }
40
57
  /** An append-only byte buffer with the wire's own vocabulary. */
41
58
  export class ByteWriter {
42
59
  chunks = [];
@@ -9,7 +9,7 @@ export interface AgentIdentityFields {
9
9
  }
10
10
  /** The form the product stores, which is the form the signature covers. */
11
11
  export declare function parseIdentityFields(fields: AgentIdentityFields): Required<AgentIdentityFields>;
12
- /** The exact bytes the owning address signs to name an agent. */
12
+ /** The exact message the owning address signs to name an agent. */
13
13
  export declare function canonicalIdentityPayload(edit: {
14
14
  agentId: string;
15
15
  owner: string;
package/dist/identity.js CHANGED
@@ -6,14 +6,14 @@
6
6
  * and names it. After a claim the same route belongs to the claiming wallet,
7
7
  * and this key can no longer sign for it -- which is the point of a claim.
8
8
  *
9
- * The payload mirrors `AgentIdentityEdit::canonical_payload` in
9
+ * The message mirrors `AgentIdentityEdit::canonical_message` in
10
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";
11
+ * ids render at a fixed 32 bytes and every named field is quoted, so "ab"/"c"
12
+ * and "a"/"bc" cannot sign the same text and a newline inside a name cannot
13
+ * forge a line of its own. The product parses before it verifies -- it trims
14
+ * each field and lower-cases the handle -- so this signs the parsed form, or
15
+ * the signature is over text the store never holds. */
16
+ import { fixedHex0x, signedMessage } from "./bytes.js";
17
17
  import { signOwnerAuthenticator } from "./keypair.js";
18
18
  import { refusalMessageFromText } from "./refusal.js";
19
19
  const IDENTITY_DOMAIN = "dopa_open::agent_identity::v1";
@@ -27,20 +27,18 @@ export function parseIdentityFields(fields) {
27
27
  bio: fields.bio?.trim() || null,
28
28
  };
29
29
  }
30
- /** The exact bytes the owning address signs to name an agent. */
30
+ /** The exact message the owning address signs to name an agent. */
31
31
  export function canonicalIdentityPayload(edit) {
32
- const domain = textBytes(IDENTITY_DOMAIN);
33
32
  const parsed = parseIdentityFields(edit.fields);
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);
42
- }
43
- return writer.pushU64(edit.issuedAtMs).pushU64(edit.expiresAtMs).bytes();
33
+ return signedMessage(IDENTITY_DOMAIN, [
34
+ ["agent", fixedHex0x(edit.agentId, 32, "agent id")],
35
+ ["owner", fixedHex0x(edit.owner, 32, "owner")],
36
+ ["name", JSON.stringify(parsed.name ?? "")],
37
+ ["handle", JSON.stringify(parsed.handle ?? "")],
38
+ ["bio", JSON.stringify(parsed.bio ?? "")],
39
+ ["issued_at_ms", String(edit.issuedAtMs)],
40
+ ["expires_at_ms", String(edit.expiresAtMs)],
41
+ ]);
44
42
  }
45
43
  /** Name an agent, as the address that owns it. Answers what the arena stored. */
46
44
  export async function nameAgent(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dopamint-fun/open-sdk",
3
- "version": "0.2.0-dev.10",
3
+ "version": "0.2.0-dev.12",
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": {