@vlayer/sdk 0.1.0-nightly-20241204-02d2f89 → 0.1.0-nightly-20241206-b638b72

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.
@@ -2,9 +2,10 @@ import { Email } from "postal-mime";
2
2
  export declare class DkimParsingError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
- export declare function parseEmail(mime: string): Promise<Email>;
6
- export declare function getDkimSigners(mail: Email): {
5
+ export interface DkimDomainSelector {
7
6
  domain: string;
8
7
  selector: string;
9
- }[];
8
+ }
9
+ export declare function parseEmail(mime: string): Promise<Email>;
10
+ export declare function getDkimSigners(mail: Email): DkimDomainSelector[];
10
11
  export declare function parseParams(str: string): Record<string, string>;
@@ -1,3 +1,5 @@
1
+ import { DkimDomainSelector } from "./parseEmail.js";
2
+ export declare function findIndicesOfMatchingDomains(signers: DkimDomainSelector[], expectedOrigin: string): number[];
1
3
  export declare function preverifyEmail(mimeEmail: string): Promise<{
2
4
  email: string;
3
5
  dnsRecords: string[];
@@ -1,13 +1,35 @@
1
- import { parseEmail, getDkimSigners } from "./parseEmail.js";
1
+ import { getDkimSigners, parseEmail } from "./parseEmail.js";
2
2
  import { resolveDkimDns } from "./dnsResolver.js";
3
+ import { prefixAllButNthSubstring } from "../utils/prefixAllButNthSubstring.js";
4
+ export function findIndicesOfMatchingDomains(signers, expectedOrigin) {
5
+ return signers
6
+ .map(({ domain }) => expectedOrigin.endsWith(domain))
7
+ .map((isMatch, index) => (isMatch ? index : -1))
8
+ .filter((index) => index !== -1);
9
+ }
10
+ function requireSameOrigin(mimeEmail, signers, fromAddress) {
11
+ const matchingIndices = findIndicesOfMatchingDomains(signers, fromAddress);
12
+ if (matchingIndices.length != 1) {
13
+ throw new Error(`Found ${matchingIndices.length} DKIM headers matching the sender domain`);
14
+ }
15
+ const [matchingIndex] = matchingIndices;
16
+ return [
17
+ prefixAllButNthSubstring(mimeEmail, /^\s*dkim-signature/gim, signers.length, matchingIndex),
18
+ [signers[matchingIndex]],
19
+ ];
20
+ }
3
21
  export async function preverifyEmail(mimeEmail) {
4
22
  const parsedEmail = await parseEmail(mimeEmail);
5
- const signers = getDkimSigners(parsedEmail);
23
+ let signers = getDkimSigners(parsedEmail);
24
+ const fromAddress = parsedEmail.from.address;
25
+ if (!fromAddress) {
26
+ throw new Error("No from address found");
27
+ }
6
28
  if (signers.length === 0) {
7
29
  throw new Error("No DKIM header found");
8
30
  }
9
31
  if (signers.length > 1) {
10
- throw new Error("Multiple DKIM headers found");
32
+ [mimeEmail, signers] = requireSameOrigin(mimeEmail, signers, fromAddress);
11
33
  }
12
34
  const [{ domain, selector }] = signers;
13
35
  const dnsRecord = await resolveDkimDns(domain, selector);
@@ -1,9 +1,9 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { preverifyEmail } from "./preverify.js";
3
2
  import { readFile } from "../../testHelpers/readFile.js";
3
+ import { findIndicesOfMatchingDomains, preverifyEmail } from "./preverify.js";
4
+ const rawEmail = readFile("./src/api/email/testdata/test_email.txt");
4
5
  describe("Preverify email: integration", () => {
5
6
  test("adds dns record to email mime", async () => {
6
- const rawEmail = readFile("./src/api/email/testdata/test_email.txt");
7
7
  const preverifiedEmail = await preverifyEmail(rawEmail);
8
8
  expect(preverifiedEmail).toMatchObject({
9
9
  email: rawEmail,
@@ -18,8 +18,92 @@ describe("Preverify email: integration", () => {
18
18
  const emailWithNoDkimHeader = readFile("./src/api/email/testdata/test_email_unknown_domain.txt");
19
19
  await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow();
20
20
  });
21
- test("throws error if multiple DNS records found", async () => {
22
- const emailWithNoDkimHeader = readFile("./src/api/email/testdata/test_email_multiple_dkims.txt");
23
- await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow("Multiple DKIM headers found");
21
+ describe("multiple DKIM headers", () => {
22
+ function addDkimWithDomain(domain, email) {
23
+ return `DKIM-Signature: v=1; a=rsa-sha256; d=${domain};
24
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
25
+ ${email}`;
26
+ }
27
+ function addFakeDkimWithDomain(domain, email) {
28
+ return `X-${addDkimWithDomain(domain, email)}`;
29
+ }
30
+ test("looks for DKIM header with the domain matching the sender and removes all other DKIM headers", async () => {
31
+ const emailWithAddedHeaders = [
32
+ "example.com",
33
+ "hello.kitty",
34
+ "google.com",
35
+ ].reduce((email, domain) => addDkimWithDomain(domain, email), rawEmail);
36
+ const email = await preverifyEmail(emailWithAddedHeaders);
37
+ expect(email.email
38
+ .startsWith(`X-DKIM-Signature: v=1; a=rsa-sha256; d=google.com;
39
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
40
+ X-DKIM-Signature: v=1; a=rsa-sha256; d=hello.kitty;
41
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
42
+ DKIM-Signature: v=1; a=rsa-sha256; d=example.com;
43
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
44
+ X-DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r
45
+ c=simple/simple; d=google.com;`)).toBeTruthy();
46
+ expect(email.dnsRecords).toStrictEqual(["v=DKIM1; p="]);
47
+ });
48
+ test("throws error if no DNS record domain matches the sender", async () => {
49
+ const emailWithAddedHeaders = addDkimWithDomain("otherdomain.com", rawEmail);
50
+ await expect(preverifyEmail(emailWithAddedHeaders)).rejects.toThrow("Found 0 DKIM headers matching the sender domain");
51
+ });
52
+ test("throws error if multiple DNS record domains match the sender", async () => {
53
+ let emailWithAddedHeaders = addDkimWithDomain("example.com", rawEmail);
54
+ emailWithAddedHeaders = addDkimWithDomain("example.com", emailWithAddedHeaders);
55
+ await expect(preverifyEmail(emailWithAddedHeaders)).rejects.toThrow("Found 2 DKIM headers matching the sender domain");
56
+ });
57
+ test("ignores x-dkim-signature headers", async () => {
58
+ const emailWithPrefixedDkim = addDkimWithDomain("example.com", addFakeDkimWithDomain("example.com", rawEmail));
59
+ const email = await preverifyEmail(emailWithPrefixedDkim);
60
+ expect(email.email
61
+ .startsWith(`DKIM-Signature: v=1; a=rsa-sha256; d=example.com;
62
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
63
+ X-DKIM-Signature: v=1; a=rsa-sha256; d=example.com;
64
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
65
+ X-DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r
66
+ c=simple/simple; d=google.com;`)).toBeTruthy();
67
+ });
68
+ test("ignores dkim-signature somewhere inside a header", async () => {
69
+ const headerWithDkim = `WTF-IS-THIS-HEADER: DKIM-SIGNATURE;`;
70
+ const emailWithDkimInHeader = `${headerWithDkim}\n${addDkimWithDomain("example.com", rawEmail)}`;
71
+ const email = await preverifyEmail(emailWithDkimInHeader);
72
+ expect(email.email.startsWith(`WTF-IS-THIS-HEADER: DKIM-SIGNATURE;
73
+ DKIM-Signature: v=1; a=rsa-sha256; d=example.com;
74
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
75
+ X-DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r
76
+ c=simple/simple; d=google.com;`)).toBeTruthy();
77
+ });
78
+ test("ignores dkim-signature somewhere inside a body", async () => {
79
+ const emailWithAddedDkim = addDkimWithDomain("example.com", rawEmail);
80
+ const emailWithDkimsInBody = `${emailWithAddedDkim}\ndkim-signature dkim-signature\r\ndkim-signature`;
81
+ const email = await preverifyEmail(emailWithDkimsInBody);
82
+ expect(email.email
83
+ .startsWith(`DKIM-Signature: v=1; a=rsa-sha256; d=example.com;
84
+ s=selector; c=relaxed/relaxed; q=dns/txt; bh=; h=From:Subject:Date:To; b=
85
+ X-DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r
86
+ c=simple/simple; d=google.com;`)).toBeTruthy();
87
+ expect(email.email.endsWith(`\ndkim-signature dkim-signature\r\ndkim-signature`)).toBeTruthy();
88
+ });
89
+ });
90
+ });
91
+ describe("findIndicesOfMatchingDomains", () => {
92
+ test("returns indices of matching domains", () => {
93
+ const signers = [
94
+ { domain: "example.com", selector: "selector1" },
95
+ { domain: "other.other", selector: "selector2" },
96
+ { domain: "example.com", selector: "selector3" },
97
+ ];
98
+ expect(findIndicesOfMatchingDomains(signers, "example.com")).toStrictEqual([
99
+ 0, 2,
100
+ ]);
101
+ });
102
+ test("returns empty array if no matching domains", () => {
103
+ const signers = [
104
+ { domain: "example.com", selector: "selector1" },
105
+ { domain: "example.org", selector: "selector2" },
106
+ ];
107
+ expect(findIndicesOfMatchingDomains(signers, "other.other")).toStrictEqual([]);
24
108
  });
25
109
  });
@@ -1,4 +1,4 @@
1
- import { prove } from "../prover.js";
1
+ import { prove, getProofReceipt } from "../prover.js";
2
2
  import { createExtensionWebProofProvider } from "../webProof/index.js";
3
3
  import { decodeFunctionResult, } from "viem";
4
4
  import { ZkProvingStatus } from "../../web-proof-commons/index.js";
@@ -10,7 +10,7 @@ function dropEmptyProofFromArgs(args) {
10
10
  }
11
11
  async function getHash(vcall_response) {
12
12
  const result = await vcall_response;
13
- return [result.result.hash, result];
13
+ return result.result;
14
14
  }
15
15
  export const createVlayerClient = ({ url = "http://127.0.0.1:3000", webProofProvider = createExtensionWebProofProvider(), } = {
16
16
  url: "http://127.0.0.1:3000",
@@ -29,21 +29,17 @@ export const createVlayerClient = ({ url = "http://127.0.0.1:3000", webProofProv
29
29
  webProofProvider.notifyZkProvingStatus(ZkProvingStatus.Done);
30
30
  return result;
31
31
  });
32
- const [hash, result_promise] = await getHash(response);
33
- resultHashMap.set(hash, [
34
- Promise.resolve(result_promise),
35
- proverAbi,
36
- functionName,
37
- ]);
32
+ const hash = await getHash(response);
33
+ resultHashMap.set(hash, [proverAbi, functionName]);
38
34
  return { hash };
39
35
  },
40
36
  waitForProvingResult: async ({ hash, }) => {
37
+ const { result: { proof, evm_call_result }, } = await getProofReceipt({ hash }, url);
41
38
  const savedProvingData = resultHashMap.get(hash);
42
39
  if (!savedProvingData) {
43
40
  throw new Error("No result found for hash " + hash);
44
41
  }
45
- const [result_promise, proverAbi, functionName] = savedProvingData;
46
- const { result: { proof, evm_call_result }, } = await result_promise;
42
+ const [proverAbi, functionName] = savedProvingData;
47
43
  const result = dropEmptyProofFromArgs(decodeFunctionResult({
48
44
  abi: proverAbi,
49
45
  data: evm_call_result,
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
1
+ import { describe, expect, it, vi, beforeEach, beforeAll, } from "vitest";
2
2
  import { createExtensionWebProofProvider } from "../webProof/index.js";
3
3
  import { createVlayerClient } from "./client.js";
4
4
  import { ZkProvingStatus } from "src/web-proof-commons";
@@ -28,35 +28,46 @@ function generateRandomHash() {
28
28
  return hash;
29
29
  }
30
30
  describe("Success zk-proving", () => {
31
- beforeEach(() => {
32
- fetchMocker.mockResponseOnce((req) => {
33
- if (req.url === "http://127.0.0.1:3000/") {
34
- return {
35
- body: JSON.stringify({
36
- result: {
37
- hash: generateRandomHash(),
38
- proof: {},
39
- },
40
- }),
41
- };
42
- }
43
- return {};
44
- });
45
- });
46
- it("should send message to extension that zkproving started and then that is done", async () => {
31
+ let hash;
32
+ let zkProvingSpy;
33
+ let vlayer;
34
+ beforeAll(() => {
35
+ hash = generateRandomHash();
47
36
  const webProofProvider = createExtensionWebProofProvider();
48
- const zkProvingSpy = vi.spyOn(webProofProvider, "notifyZkProvingStatus");
49
- const vlayer = createVlayerClient({ webProofProvider });
50
- const hash = await vlayer.prove({
37
+ zkProvingSpy = vi.spyOn(webProofProvider, "notifyZkProvingStatus");
38
+ vlayer = createVlayerClient({ webProofProvider });
39
+ });
40
+ it("should send message to extension that zkproving started", async () => {
41
+ fetchMocker.mockResponseOnce(() => {
42
+ return {
43
+ body: JSON.stringify({
44
+ result: hash,
45
+ }),
46
+ };
47
+ });
48
+ const result = await vlayer.prove({
51
49
  address: `0x${"a".repeat(40)}`,
52
50
  functionName: "main",
53
51
  proverAbi: [],
54
52
  args: [],
55
53
  chainId: 42,
56
54
  });
57
- await vlayer.waitForProvingResult(hash);
55
+ expect(result.hash).toBe(hash);
58
56
  expect(zkProvingSpy).toBeCalledTimes(2);
59
57
  expect(zkProvingSpy).toHaveBeenNthCalledWith(1, ZkProvingStatus.Proving);
58
+ });
59
+ it("should send message to extension that zkproving is done", async () => {
60
+ fetchMocker.mockResponseOnce(() => {
61
+ return {
62
+ body: JSON.stringify({
63
+ result: {
64
+ proof: {},
65
+ },
66
+ }),
67
+ };
68
+ });
69
+ await vlayer.waitForProvingResult({ hash });
70
+ expect(zkProvingSpy).toBeCalledTimes(2);
60
71
  expect(zkProvingSpy).toHaveBeenNthCalledWith(2, ZkProvingStatus.Done);
61
72
  });
62
73
  });
@@ -7,7 +7,7 @@ export type ContractSpec = {
7
7
  abi: Abi;
8
8
  bytecode: Bytecode;
9
9
  };
10
- export type ContractArg = number | string | boolean | bigint | number[] | string[] | boolean[] | bigint[] | Address[];
10
+ export type ContractArg = number | string | boolean | bigint | number[] | string[] | boolean[] | bigint[] | Address[] | (string | bigint)[] | (string | bigint)[][];
11
11
  export type EthereumAddress = Branded<Hex, "EthereumAddress">;
12
12
  export type EthereumTxHash = Branded<Hex, "EthereumTxHash">;
13
13
  export declare function assertEthereumAddress(hash: string): asserts hash is EthereumAddress;
@@ -29,14 +29,22 @@ export type Proof = {
29
29
  settleBlockNumber: bigint;
30
30
  };
31
31
  };
32
- export interface VCallResult {
32
+ export type VCallResult = Hex;
33
+ export interface VCallResponse {
34
+ jsonrpc: string;
35
+ result: VCallResult;
36
+ id: number;
37
+ }
38
+ export type VGetProofReceiptParams = {
33
39
  hash: Hex;
40
+ };
41
+ export interface VGetProofReceiptResult {
34
42
  evm_call_result: Hex;
35
43
  proof: Proof;
36
44
  }
37
- export interface VCallResponse {
45
+ export interface VGetProofReceiptResponse {
38
46
  jsonrpc: string;
39
- result: VCallResult;
47
+ result: VGetProofReceiptResult;
40
48
  id: number;
41
49
  }
42
50
  export type VlayerClient = {
@@ -1,5 +1,7 @@
1
1
  import { type Abi, AbiStateMutability, type Address, ContractFunctionArgs, ContractFunctionName } from "viem";
2
+ import { type BrandedHash } from "./lib/types/vlayer.js";
2
3
  export interface ProveOptions {
3
4
  preverifyVersions?: boolean;
4
5
  }
5
6
  export declare function prove<T extends Abi, F extends ContractFunctionName<T>>(prover: Address, abi: T, functionName: F, args: ContractFunctionArgs<T, AbiStateMutability, F>, chainId?: number, gasLimit?: number, url?: string, options?: ProveOptions): Promise<import("./lib/types/vlayer.js").VCallResponse>;
7
+ export declare function getProofReceipt<T extends Abi, F extends ContractFunctionName<T>>(hash: BrandedHash<T, F>, url?: string): Promise<import("./lib/types/vlayer.js").VGetProofReceiptResponse>;
@@ -1,5 +1,6 @@
1
1
  import { encodeFunctionData, } from "viem";
2
2
  import { v_call } from "./v_call.js";
3
+ import { v_getProofReceipt } from "./v_getProofReceipt.js";
3
4
  import { foundry } from "viem/chains";
4
5
  import { v_versions } from "./v_versions.js";
5
6
  import { checkVersionCompatibility } from "./utils/versions.js";
@@ -25,3 +26,9 @@ export async function prove(prover, abi, functionName, args, chainId = foundry.i
25
26
  };
26
27
  return v_call(call, context, url);
27
28
  }
29
+ export async function getProofReceipt(hash, url = "http://127.0.0.1:3000") {
30
+ const params = {
31
+ hash: hash.hash,
32
+ };
33
+ return v_getProofReceipt(params, url);
34
+ }
@@ -0,0 +1 @@
1
+ export declare function prefixAllButNthSubstring(str: string, pattern: RegExp, substringsCount: number, skippedIndex: number): string;
@@ -0,0 +1,8 @@
1
+ export function prefixAllButNthSubstring(str, pattern, substringsCount, skippedIndex) {
2
+ let occurrence = 0;
3
+ return str.replace(pattern, (match) => {
4
+ return occurrence++ === skippedIndex || occurrence > substringsCount
5
+ ? match
6
+ : `X-${match}`;
7
+ });
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { prefixAllButNthSubstring } from "./prefixAllButNthSubstring.js";
3
+ describe("prefixAllButNthSubstring", () => {
4
+ test("adds 'X-' prefix to all matches except n-th (indexed from 0)", () => {
5
+ const str = "abc 123 abc 456 abc 789";
6
+ expect(prefixAllButNthSubstring(str, /abc/gi, 3, 0)).toBe("abc 123 X-abc 456 X-abc 789");
7
+ expect(prefixAllButNthSubstring(str, /abc/gi, 3, 1)).toBe("X-abc 123 abc 456 X-abc 789");
8
+ expect(prefixAllButNthSubstring(str, /abc/gi, 3, 2)).toBe("X-abc 123 X-abc 456 abc 789");
9
+ });
10
+ test("does not add prefix to substrings past total substring count", () => {
11
+ const str = "abc 123 abc 456 abc 789 abc abc";
12
+ expect(prefixAllButNthSubstring(str, /abc/gi, 3, 1)).toBe("X-abc 123 abc 456 X-abc 789 abc abc");
13
+ });
14
+ });
@@ -0,0 +1,2 @@
1
+ import { VGetProofReceiptParams, VGetProofReceiptResponse } from "./lib/types/vlayer.js";
2
+ export declare function v_getProofReceipt(params: VGetProofReceiptParams, url?: string): Promise<VGetProofReceiptResponse>;
@@ -0,0 +1,32 @@
1
+ import { parseVCallResponseError } from "./lib/errors.js";
2
+ function v_getProofReceiptBody(params) {
3
+ return {
4
+ method: "v_getProofReceipt",
5
+ params: params,
6
+ id: 1,
7
+ jsonrpc: "2.0",
8
+ };
9
+ }
10
+ export async function v_getProofReceipt(params, url = "http://127.0.0.1:3000") {
11
+ const response = await fetch(url, {
12
+ method: "POST",
13
+ body: JSON.stringify(v_getProofReceiptBody(params)),
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ console.log("response", response);
17
+ if (!response.ok) {
18
+ throw new Error(`HTTP error! status: ${response.status}`);
19
+ }
20
+ const response_json = await response.json();
21
+ console.log("response_json", response_json);
22
+ assertObject(response_json);
23
+ if ("error" in response_json) {
24
+ throw parseVCallResponseError(response_json.error);
25
+ }
26
+ return response_json;
27
+ }
28
+ function assertObject(x) {
29
+ if (typeof x !== "object") {
30
+ throw new Error("Expected object");
31
+ }
32
+ }
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "types": "./dist/config/index.d.ts"
16
16
  }
17
17
  },
18
- "version": "0.1.0-nightly-20241204-02d2f89",
18
+ "version": "0.1.0-nightly-20241206-b638b72",
19
19
  "scripts": {
20
20
  "build": "bun tsc && bun tsc-alias",
21
21
  "test:unit": "vitest --run",