prividium 0.15.0 → 0.16.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/README.md +12 -0
- package/dist/sdk/create-prividium-client.js +2 -1
- package/dist/sdk/index.d.ts +1 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/popup-auth.js +9 -0
- package/dist/sdk/selective-disclosure/actions.d.ts +11 -0
- package/dist/sdk/selective-disclosure/actions.js +21 -0
- package/dist/sdk/selective-disclosure/batch-info.d.ts +2 -0
- package/dist/sdk/selective-disclosure/batch-info.js +14 -0
- package/dist/sdk/selective-disclosure/disclosure-result.d.ts +36 -0
- package/dist/sdk/selective-disclosure/disclosure-result.js +1 -0
- package/dist/sdk/selective-disclosure/index.d.ts +8 -0
- package/dist/sdk/selective-disclosure/index.js +8 -0
- package/dist/sdk/selective-disclosure/state-commitment.d.ts +2 -0
- package/dist/sdk/selective-disclosure/state-commitment.js +12 -0
- package/dist/sdk/selective-disclosure/token-balance-disclosure.d.ts +7 -0
- package/dist/sdk/selective-disclosure/token-balance-disclosure.js +1 -0
- package/dist/sdk/selective-disclosure/token-supply-disclosure.d.ts +7 -0
- package/dist/sdk/selective-disclosure/token-supply-disclosure.js +1 -0
- package/dist/sdk/selective-disclosure/utils.d.ts +2 -0
- package/dist/sdk/selective-disclosure/utils.js +14 -0
- package/dist/sdk/selective-disclosure/verifiy-proofs.d.ts +31 -0
- package/dist/sdk/selective-disclosure/verifiy-proofs.js +85 -0
- package/dist/sdk/selective-disclosure/verify-disclosure.d.ts +28 -0
- package/dist/sdk/selective-disclosure/verify-disclosure.js +88 -0
- package/dist/tsconfig.sdk.tsbuildinfo +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -577,6 +577,18 @@ Environment variables (optional):
|
|
|
577
577
|
|
|
578
578
|
Precedence: CLI flags > environment variables > saved config file.
|
|
579
579
|
|
|
580
|
+
### Local Development
|
|
581
|
+
|
|
582
|
+
When running the full Prividium stack locally (`pnpm dev`), use:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
npx prividium proxy \
|
|
586
|
+
--rpc-url http://localhost:8000 \
|
|
587
|
+
--user-panel-url http://localhost:3001
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Point Foundry, Hardhat, or your scripts at `http://127.0.0.1:24101/rpc` after sign-in.
|
|
591
|
+
|
|
580
592
|
### Config Commands
|
|
581
593
|
|
|
582
594
|
```bash
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createClient, publicActions } from 'viem';
|
|
2
|
+
import { selectiveDisclosureActions } from './selective-disclosure/actions';
|
|
2
3
|
function prividiumActions(client) {
|
|
3
4
|
return {
|
|
4
5
|
call: (args) => {
|
|
@@ -21,5 +22,5 @@ export function createPrividiumClient(config) {
|
|
|
21
22
|
name,
|
|
22
23
|
type: 'publicClient'
|
|
23
24
|
});
|
|
24
|
-
return client.extend(publicActions).extend(prividiumActions);
|
|
25
|
+
return client.extend(publicActions).extend(prividiumActions).extend(selectiveDisclosureActions);
|
|
25
26
|
}
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createPrividiumClient } from './create-prividium-client.js';
|
|
|
2
2
|
export { type AuthCallbackMessage, handleAuthCallback, type OauthScope, PopupAuth } from './popup-auth.js';
|
|
3
3
|
export { createPrividiumChain } from './prividium-chain.js';
|
|
4
4
|
export { FORBIDDEN_ERROR_CODE, UNAUTHORIZED_ERROR_CODE } from './rpc-error-codes.js';
|
|
5
|
+
export * from './selective-disclosure/index.js';
|
|
5
6
|
export { LocalStorage, TokenManager } from './storage.js';
|
|
6
7
|
export { generateRandomState } from './token-utils.js';
|
|
7
8
|
export type { AddNetworkParams, ContractAbiResponse, PopupOptions, PrividiumChain, PrividiumConfig, Storage, TokenData, UserProfile, UserRole } from './types.js';
|
package/dist/sdk/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { createPrividiumClient } from './create-prividium-client.js';
|
|
|
2
2
|
export { handleAuthCallback, PopupAuth } from './popup-auth.js';
|
|
3
3
|
export { createPrividiumChain } from './prividium-chain.js';
|
|
4
4
|
export { FORBIDDEN_ERROR_CODE, UNAUTHORIZED_ERROR_CODE } from './rpc-error-codes.js';
|
|
5
|
+
export * from './selective-disclosure/index.js';
|
|
5
6
|
export { LocalStorage, TokenManager } from './storage.js';
|
|
6
7
|
export { generateRandomState } from './token-utils.js';
|
|
7
8
|
export { AUTH_ERRORS, STORAGE_KEYS } from './types.js';
|
package/dist/sdk/popup-auth.js
CHANGED
|
@@ -37,6 +37,15 @@ export class PopupAuth {
|
|
|
37
37
|
console.warn(`Received message from unexpected origin: ${event.origin}`);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
// Stop polling for popup.closed now that a definitive response
|
|
41
|
+
// has arrived. Otherwise, the callback page self-closes (~100ms
|
|
42
|
+
// after posting) while setToken() is still awaiting token
|
|
43
|
+
// expiration, and the poll can reject with "Authentication was
|
|
44
|
+
// cancelled" even though the token gets stored successfully.
|
|
45
|
+
if (checkInterval) {
|
|
46
|
+
clearInterval(checkInterval);
|
|
47
|
+
checkInterval = null;
|
|
48
|
+
}
|
|
40
49
|
// Handle error from callback
|
|
41
50
|
if (event.data.error) {
|
|
42
51
|
popup.close();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Account, type Address, type Chain, type Client, type RpcSchema, type Transport } from 'viem';
|
|
2
|
+
import type { EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
export type SelectiveDisclosureActions = {
|
|
4
|
+
tokenSupplyDisclosure: (tokenAddress: Address, blockNumber: number | bigint) => Promise<EthCallDisclosureResult>;
|
|
5
|
+
tokenBalanceDisclosure: (tokenAddress: Address, holderAddress: Address, blockNumber: number | bigint) => Promise<EthCallDisclosureResult>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Viem action extender that adds Prividium selective-disclosure RPC methods
|
|
9
|
+
* to a client. Use with `client.extend(selectiveDisclosureActions)`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function selectiveDisclosureActions<transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, accountOrAddress extends Account | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined>(client: Client<transport, chain, accountOrAddress, rpcSchema>): SelectiveDisclosureActions;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { numberToHex } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* Viem action extender that adds Prividium selective-disclosure RPC methods
|
|
4
|
+
* to a client. Use with `client.extend(selectiveDisclosureActions)`.
|
|
5
|
+
*/
|
|
6
|
+
export function selectiveDisclosureActions(client) {
|
|
7
|
+
return {
|
|
8
|
+
async tokenSupplyDisclosure(tokenAddress, blockNumber) {
|
|
9
|
+
return client.request({
|
|
10
|
+
method: 'prividium_tokenSupplyDisclosure',
|
|
11
|
+
params: [tokenAddress, numberToHex(blockNumber)]
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
async tokenBalanceDisclosure(tokenAddress, holderAddress, blockNumber) {
|
|
15
|
+
return client.request({
|
|
16
|
+
method: 'prividium_tokenBalanceDisclosure',
|
|
17
|
+
params: [tokenAddress, holderAddress, numberToHex(blockNumber)]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { encodeAbiParameters, pad, parseAbiParameters } from 'viem';
|
|
2
|
+
export function encodeBatchInfo(batchNumber, stateCommitment, numberOfLayer1Txs, priorityOperationsHash, l2ToL1LogsRootHash, commitment) {
|
|
3
|
+
return encodeAbiParameters(parseAbiParameters('uint64, bytes32, uint64, uint256, bytes32, bytes32, bytes32, uint256, bytes32'), [
|
|
4
|
+
batchNumber,
|
|
5
|
+
stateCommitment,
|
|
6
|
+
0n, // indexRepeatedStorageChanges
|
|
7
|
+
numberOfLayer1Txs,
|
|
8
|
+
priorityOperationsHash,
|
|
9
|
+
pad('0x00'), // dependencyRootsRollingHash
|
|
10
|
+
l2ToL1LogsRootHash,
|
|
11
|
+
0n, // timestamp
|
|
12
|
+
commitment
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Hex } from 'viem';
|
|
2
|
+
import type { StorageSlotProof } from './verifiy-proofs';
|
|
3
|
+
export type StateCommitmentPreimage = {
|
|
4
|
+
nextFreeSlot: Hex;
|
|
5
|
+
blockNumber: Hex;
|
|
6
|
+
last256BlockHashesBlake: Hex;
|
|
7
|
+
lastBlockTimestamp: Hex;
|
|
8
|
+
};
|
|
9
|
+
export type L1VerificationData = {
|
|
10
|
+
batchNumber: number;
|
|
11
|
+
numberOfLayer1Txs: number;
|
|
12
|
+
priorityOperationsHash: Hex;
|
|
13
|
+
dependencyRootsRollingHash: Hex;
|
|
14
|
+
l2ToL1LogsRootHash: Hex;
|
|
15
|
+
commitment: Hex;
|
|
16
|
+
};
|
|
17
|
+
export type StorageProof = {
|
|
18
|
+
key: Hex;
|
|
19
|
+
proof: StorageSlotProof;
|
|
20
|
+
};
|
|
21
|
+
export type AccountStorageProofs = {
|
|
22
|
+
address: Hex;
|
|
23
|
+
storageProofs: StorageProof[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Shared response shape returned by all token-related selective-disclosure
|
|
27
|
+
* RPC methods (`prividium_tokenSupplyDisclosure`, `prividium_tokenBalanceDisclosure`).
|
|
28
|
+
*/
|
|
29
|
+
export type EthCallDisclosureResult = {
|
|
30
|
+
result: Hex;
|
|
31
|
+
callData: Hex;
|
|
32
|
+
batchNumber: number;
|
|
33
|
+
stateCommitmentPreimage: StateCommitmentPreimage;
|
|
34
|
+
l1VerificationData: L1VerificationData;
|
|
35
|
+
proofs: AccountStorageProofs[];
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './actions';
|
|
2
|
+
export * from './batch-info';
|
|
3
|
+
export * from './disclosure-result';
|
|
4
|
+
export * from './state-commitment';
|
|
5
|
+
export * from './token-balance-disclosure';
|
|
6
|
+
export * from './token-supply-disclosure';
|
|
7
|
+
export * from './verifiy-proofs';
|
|
8
|
+
export * from './verify-disclosure';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './actions';
|
|
2
|
+
export * from './batch-info';
|
|
3
|
+
export * from './disclosure-result';
|
|
4
|
+
export * from './state-commitment';
|
|
5
|
+
export * from './token-balance-disclosure';
|
|
6
|
+
export * from './token-supply-disclosure';
|
|
7
|
+
export * from './verifiy-proofs';
|
|
8
|
+
export * from './verify-disclosure';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes } from 'viem';
|
|
2
|
+
import { blake2s256, concatBytes } from './utils';
|
|
3
|
+
export function computeStateCommitment(treeRoot, nextFreeSlot, blockNumber, last256BlockHashesBlake, lastBlockTimestamp) {
|
|
4
|
+
const nextFreeSlotBytes = new Uint8Array(8);
|
|
5
|
+
new DataView(nextFreeSlotBytes.buffer).setBigUint64(0, nextFreeSlot, false); // big-endian
|
|
6
|
+
const blockNumberBytes = new Uint8Array(8);
|
|
7
|
+
new DataView(blockNumberBytes.buffer).setBigUint64(0, blockNumber, false);
|
|
8
|
+
const timestampBytes = new Uint8Array(8);
|
|
9
|
+
new DataView(timestampBytes.buffer).setBigUint64(0, lastBlockTimestamp, false);
|
|
10
|
+
const bytes = blake2s256(concatBytes(hexToBytes(treeRoot), nextFreeSlotBytes, blockNumberBytes, hexToBytes(last256BlockHashesBlake), timestampBytes));
|
|
11
|
+
return bytesToHex(bytes);
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem';
|
|
2
|
+
import type { EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
export type TokenBalanceDisclosureRpc = {
|
|
4
|
+
Method: 'prividium_tokenBalanceDisclosure';
|
|
5
|
+
Parameters: [tokenAddress: Address, holderAddress: Address, blockNumber: Hex];
|
|
6
|
+
ReturnType: EthCallDisclosureResult;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem';
|
|
2
|
+
import type { EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
export type TokenSupplyDisclosureRpc = {
|
|
4
|
+
Method: 'prividium_tokenSupplyDisclosure';
|
|
5
|
+
Parameters: [tokenAddress: Address, blockNumber: Hex];
|
|
6
|
+
ReturnType: EthCallDisclosureResult;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { blake2s } from 'blakejs';
|
|
2
|
+
export function concatBytes(...arrays) {
|
|
3
|
+
const totalSize = arrays.map((bytes) => bytes.length).reduce((a, b) => a + b);
|
|
4
|
+
const result = new Uint8Array(totalSize);
|
|
5
|
+
let offset = 0;
|
|
6
|
+
for (const arr of arrays) {
|
|
7
|
+
result.set(arr, offset);
|
|
8
|
+
offset += arr.length;
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
export function blake2s256(data) {
|
|
13
|
+
return blake2s(data, undefined, 32);
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
|
+
export type StorageRoot = {
|
|
3
|
+
treeRoot: Hex;
|
|
4
|
+
value: Hex | null;
|
|
5
|
+
};
|
|
6
|
+
export interface ExistenceProof {
|
|
7
|
+
type: 'existing';
|
|
8
|
+
index: number;
|
|
9
|
+
value: Hex;
|
|
10
|
+
nextIndex: number;
|
|
11
|
+
siblings: Hex[];
|
|
12
|
+
}
|
|
13
|
+
export interface NonExistenceProof {
|
|
14
|
+
type: 'nonExisting';
|
|
15
|
+
leftNeighbor: {
|
|
16
|
+
index: number;
|
|
17
|
+
leafKey: Hex;
|
|
18
|
+
value: Hex;
|
|
19
|
+
nextIndex: number;
|
|
20
|
+
siblings: Hex[];
|
|
21
|
+
};
|
|
22
|
+
rightNeighbor: {
|
|
23
|
+
index: number;
|
|
24
|
+
leafKey: Hex;
|
|
25
|
+
value: Hex;
|
|
26
|
+
nextIndex: number;
|
|
27
|
+
siblings: Hex[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export type StorageSlotProof = ExistenceProof | NonExistenceProof;
|
|
31
|
+
export declare function calculateStateMerkleRoot(proof: StorageSlotProof, address: Hex, storageSlot: Hex): StorageRoot;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes, pad } from 'viem';
|
|
2
|
+
import { blake2s256, concatBytes } from './utils';
|
|
3
|
+
function deriveFlatKey(address, storageKey) {
|
|
4
|
+
// blake2s(pad32(address) || storageKey)
|
|
5
|
+
const addressBytes = hexToBytes(pad(address));
|
|
6
|
+
const keyBytes = hexToBytes(storageKey);
|
|
7
|
+
return blake2s256(concatBytes(addressBytes, keyBytes));
|
|
8
|
+
}
|
|
9
|
+
function hashLeaf(leafKey, value, nextIndex) {
|
|
10
|
+
// blake2s(leafKey || value || nextIndex.to_le_bytes(8))
|
|
11
|
+
const nextIndexBytes = new Uint8Array(8);
|
|
12
|
+
const view = new DataView(nextIndexBytes.buffer);
|
|
13
|
+
view.setBigUint64(0, nextIndex, true); // little-endian
|
|
14
|
+
return blake2s256(concatBytes(leafKey, value, nextIndexBytes));
|
|
15
|
+
}
|
|
16
|
+
const TREE_DEPTH = 64;
|
|
17
|
+
function computeEmptyHashes() {
|
|
18
|
+
const hashes = new Array(TREE_DEPTH);
|
|
19
|
+
// emptyHash[0] = blake2s(0x00{72}) — empty leaf (key=0, value=0, next=0)
|
|
20
|
+
hashes[0] = blake2s256(new Uint8Array(72));
|
|
21
|
+
for (let i = 1; i < TREE_DEPTH; i++) {
|
|
22
|
+
hashes[i] = blake2s256(concatBytes(hashes[i - 1], hashes[i - 1]));
|
|
23
|
+
}
|
|
24
|
+
return hashes;
|
|
25
|
+
}
|
|
26
|
+
const EMPTY_HASHES = computeEmptyHashes();
|
|
27
|
+
function walkMerklePath(leafHash, index, siblings) {
|
|
28
|
+
// Pad siblings with empty hashes if shorter than TREE_DEPTH
|
|
29
|
+
const fullPath = [...siblings];
|
|
30
|
+
for (let i = siblings.length; i < TREE_DEPTH; i++) {
|
|
31
|
+
fullPath.push(EMPTY_HASHES[i]);
|
|
32
|
+
}
|
|
33
|
+
let current = leafHash;
|
|
34
|
+
let idx = index;
|
|
35
|
+
for (let i = 0; i < TREE_DEPTH; i++) {
|
|
36
|
+
const sibling = fullPath[i];
|
|
37
|
+
if (idx % 2n === 0n) {
|
|
38
|
+
current = blake2s256(concatBytes(current, sibling));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
current = blake2s256(concatBytes(sibling, current));
|
|
42
|
+
}
|
|
43
|
+
idx = idx / 2n;
|
|
44
|
+
}
|
|
45
|
+
if (idx !== 0n) {
|
|
46
|
+
throw new Error(`Merkle path walk did not reach root: remaining index = ${idx}`);
|
|
47
|
+
}
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
export function calculateStateMerkleRoot(proof, address, storageSlot) {
|
|
51
|
+
if (proof.type === 'existing') {
|
|
52
|
+
const existenceProof = proof;
|
|
53
|
+
const flatKey = deriveFlatKey(address, storageSlot);
|
|
54
|
+
const siblings = existenceProof.siblings.map((s) => hexToBytes(s));
|
|
55
|
+
const leaf = hashLeaf(flatKey, hexToBytes(existenceProof.value), BigInt(existenceProof.nextIndex));
|
|
56
|
+
const root = walkMerklePath(leaf, BigInt(existenceProof.index), siblings);
|
|
57
|
+
return { treeRoot: bytesToHex(root), value: existenceProof.value };
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const nonExistenceProof = proof;
|
|
61
|
+
const flatKey = deriveFlatKey(address, storageSlot);
|
|
62
|
+
const leftSiblings = nonExistenceProof.leftNeighbor.siblings.map((s) => hexToBytes(s));
|
|
63
|
+
const leftLeaf = hashLeaf(hexToBytes(nonExistenceProof.leftNeighbor.leafKey), hexToBytes(nonExistenceProof.leftNeighbor.value), BigInt(nonExistenceProof.leftNeighbor.nextIndex));
|
|
64
|
+
const leftRoot = walkMerklePath(leftLeaf, BigInt(nonExistenceProof.leftNeighbor.index), leftSiblings);
|
|
65
|
+
const rightSiblings = nonExistenceProof.rightNeighbor.siblings.map((s) => hexToBytes(s));
|
|
66
|
+
const rightLeaf = hashLeaf(hexToBytes(nonExistenceProof.rightNeighbor.leafKey), hexToBytes(nonExistenceProof.rightNeighbor.value), BigInt(nonExistenceProof.rightNeighbor.nextIndex));
|
|
67
|
+
const rightRoot = walkMerklePath(rightLeaf, BigInt(nonExistenceProof.rightNeighbor.index), rightSiblings);
|
|
68
|
+
// Both neighbors must agree on the tree root
|
|
69
|
+
if (bytesToHex(leftRoot) !== bytesToHex(rightRoot)) {
|
|
70
|
+
throw new Error('Non-existing proof: left and right neighbors disagree on tree root');
|
|
71
|
+
}
|
|
72
|
+
// Verify the queried key falls between the neighbors
|
|
73
|
+
const flatKeyHex = bytesToHex(flatKey);
|
|
74
|
+
const leftKeyHex = nonExistenceProof.leftNeighbor.leafKey.toLowerCase();
|
|
75
|
+
const rightKeyHex = nonExistenceProof.rightNeighbor.leafKey.toLowerCase();
|
|
76
|
+
if (flatKeyHex <= leftKeyHex || flatKeyHex >= rightKeyHex) {
|
|
77
|
+
throw new Error(`Non-existing proof: flat key ${flatKeyHex} not between left ${leftKeyHex} and right ${rightKeyHex}`);
|
|
78
|
+
}
|
|
79
|
+
// Verify linked list continuity
|
|
80
|
+
if (nonExistenceProof.leftNeighbor.nextIndex !== nonExistenceProof.rightNeighbor.index) {
|
|
81
|
+
throw new Error(`Non-existing proof: left.nextIndex (${nonExistenceProof.leftNeighbor.nextIndex}) != right.index (${nonExistenceProof.rightNeighbor.index})`);
|
|
82
|
+
}
|
|
83
|
+
return { treeRoot: bytesToHex(leftRoot), value: null };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
2
|
+
import type { EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
/**
|
|
4
|
+
* Verifies an ethCall disclosure response end-to-end.
|
|
5
|
+
*
|
|
6
|
+
* Verification steps:
|
|
7
|
+
* 1. Extracts Merkle roots from all storage proofs and checks they are consistent.
|
|
8
|
+
* 2. Computes the state commitment from the tree root and preimage data.
|
|
9
|
+
* 3. Encodes the batch info, hashes it, and compares against the L1 diamond proxy's `storedBatchHash`.
|
|
10
|
+
* 4. Replays the original call on `l2Client` with state overrides (proven storage + bytecodes)
|
|
11
|
+
* and checks the result matches the disclosed value.
|
|
12
|
+
*
|
|
13
|
+
* Throws if the disclosure data is internally inconsistent (e.g. no proofs, divergent roots,
|
|
14
|
+
* or missing bytecode for a contract referenced in the proofs).
|
|
15
|
+
*
|
|
16
|
+
* Returns `true` if every check passes, `false` otherwise.
|
|
17
|
+
*
|
|
18
|
+
* @param disclosure - The response from `prividium_tokenSupplyDisclosure` or `prividium_tokenBalanceDisclosure`.
|
|
19
|
+
* @param l1Client - A viem PublicClient connected to L1, used to read `storedBatchHash` from the diamond proxy.
|
|
20
|
+
* @param l2Client - A viem PublicClient connected to a local L2 node trusted by the caller. Used to
|
|
21
|
+
* replay the disclosed call with state overrides. This must be a node the caller trusts, not the
|
|
22
|
+
* Prividium proxy, so the replay cannot be manipulated.
|
|
23
|
+
* @param diamondAddress - The address of the L1 diamond proxy contract that stores batch commitments.
|
|
24
|
+
* @param contractBytecodes - A mapping from contract address to deployed bytecode (hex) for every
|
|
25
|
+
* contract referenced in the disclosure proofs. Used for the call replay with state overrides.
|
|
26
|
+
* @param batchNumber - The L1 batch number the disclosure was computed against.
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyEthCallDisclosure(disclosure: EthCallDisclosureResult, l1Client: PublicClient, l2Client: PublicClient, diamondAddress: Address, contractBytecodes: Record<Address, Hex>, batchNumber: number): Promise<boolean>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { keccak256, pad, parseAbiItem } from 'viem';
|
|
2
|
+
import { encodeBatchInfo } from './batch-info';
|
|
3
|
+
import { computeStateCommitment } from './state-commitment';
|
|
4
|
+
import { calculateStateMerkleRoot } from './verifiy-proofs';
|
|
5
|
+
const diamondAbi = [parseAbiItem('function storedBatchHash(uint256 _batchNumber) external view returns (bytes32)')];
|
|
6
|
+
/**
|
|
7
|
+
* Verifies an ethCall disclosure response end-to-end.
|
|
8
|
+
*
|
|
9
|
+
* Verification steps:
|
|
10
|
+
* 1. Extracts Merkle roots from all storage proofs and checks they are consistent.
|
|
11
|
+
* 2. Computes the state commitment from the tree root and preimage data.
|
|
12
|
+
* 3. Encodes the batch info, hashes it, and compares against the L1 diamond proxy's `storedBatchHash`.
|
|
13
|
+
* 4. Replays the original call on `l2Client` with state overrides (proven storage + bytecodes)
|
|
14
|
+
* and checks the result matches the disclosed value.
|
|
15
|
+
*
|
|
16
|
+
* Throws if the disclosure data is internally inconsistent (e.g. no proofs, divergent roots,
|
|
17
|
+
* or missing bytecode for a contract referenced in the proofs).
|
|
18
|
+
*
|
|
19
|
+
* Returns `true` if every check passes, `false` otherwise.
|
|
20
|
+
*
|
|
21
|
+
* @param disclosure - The response from `prividium_tokenSupplyDisclosure` or `prividium_tokenBalanceDisclosure`.
|
|
22
|
+
* @param l1Client - A viem PublicClient connected to L1, used to read `storedBatchHash` from the diamond proxy.
|
|
23
|
+
* @param l2Client - A viem PublicClient connected to a local L2 node trusted by the caller. Used to
|
|
24
|
+
* replay the disclosed call with state overrides. This must be a node the caller trusts, not the
|
|
25
|
+
* Prividium proxy, so the replay cannot be manipulated.
|
|
26
|
+
* @param diamondAddress - The address of the L1 diamond proxy contract that stores batch commitments.
|
|
27
|
+
* @param contractBytecodes - A mapping from contract address to deployed bytecode (hex) for every
|
|
28
|
+
* contract referenced in the disclosure proofs. Used for the call replay with state overrides.
|
|
29
|
+
* @param batchNumber - The L1 batch number the disclosure was computed against.
|
|
30
|
+
*/
|
|
31
|
+
export async function verifyEthCallDisclosure(disclosure, l1Client, l2Client, diamondAddress, contractBytecodes, batchNumber) {
|
|
32
|
+
// Step 1: Extract Merkle roots from all storage proofs and verify they're consistent.
|
|
33
|
+
const roots = [];
|
|
34
|
+
for (const proof of disclosure.proofs) {
|
|
35
|
+
for (const storageProof of proof.storageProofs) {
|
|
36
|
+
const { treeRoot } = calculateStateMerkleRoot(storageProof.proof, proof.address, storageProof.key);
|
|
37
|
+
roots.push(treeRoot);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (roots.length === 0) {
|
|
41
|
+
throw new Error('Inconsistent data: no proofs');
|
|
42
|
+
}
|
|
43
|
+
if (new Set(roots).size !== 1) {
|
|
44
|
+
throw new Error('Inconsistent data: multiple roots for same batch');
|
|
45
|
+
}
|
|
46
|
+
// Step 2: Compute state commitment from the tree root + preimage data.
|
|
47
|
+
const stateCommitment = computeStateCommitment(roots[0], BigInt(disclosure.stateCommitmentPreimage.nextFreeSlot), BigInt(disclosure.stateCommitmentPreimage.blockNumber), disclosure.stateCommitmentPreimage.last256BlockHashesBlake, BigInt(disclosure.stateCommitmentPreimage.lastBlockTimestamp));
|
|
48
|
+
// Step 3: Encode batch info and keccak256 hash it.
|
|
49
|
+
const batchInfoHex = encodeBatchInfo(BigInt(batchNumber), stateCommitment, BigInt(disclosure.l1VerificationData.numberOfLayer1Txs), disclosure.l1VerificationData.priorityOperationsHash, disclosure.l1VerificationData.l2ToL1LogsRootHash, disclosure.l1VerificationData.commitment);
|
|
50
|
+
const batchInfoHash = keccak256(batchInfoHex);
|
|
51
|
+
// Step 4: Verify against L1 diamond proxy.
|
|
52
|
+
const storedBatchHash = await l1Client.readContract({
|
|
53
|
+
address: diamondAddress,
|
|
54
|
+
abi: diamondAbi,
|
|
55
|
+
functionName: 'storedBatchHash',
|
|
56
|
+
args: [BigInt(batchNumber)]
|
|
57
|
+
});
|
|
58
|
+
if (batchInfoHash !== storedBatchHash) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// Step 5: Replay the call on L2 with state overrides to verify the proven
|
|
62
|
+
// storage values produce the expected result.
|
|
63
|
+
const contractAddress = disclosure.proofs[0].address;
|
|
64
|
+
const stateOverride = disclosure.proofs.map((proof) => {
|
|
65
|
+
const address = proof.address.toLowerCase();
|
|
66
|
+
const code = contractBytecodes[address];
|
|
67
|
+
if (!code) {
|
|
68
|
+
throw new Error(`Missing bytecode for contract ${address}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
address,
|
|
72
|
+
code,
|
|
73
|
+
state: proof.storageProofs.map((sp) => ({
|
|
74
|
+
slot: sp.key,
|
|
75
|
+
value: sp.proof.type === 'existing' ? sp.proof.value : pad('0x0')
|
|
76
|
+
}))
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
const callResult = await l2Client.call({
|
|
80
|
+
to: contractAddress,
|
|
81
|
+
data: disclosure.callData,
|
|
82
|
+
stateOverride
|
|
83
|
+
});
|
|
84
|
+
if (!callResult.data) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return BigInt(callResult.data) === BigInt(disclosure.result);
|
|
88
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/chain-core.ts","../src/create-prividium-client.ts","../src/error-utils.ts","../src/errors.ts","../src/index.ts","../src/memory-storage.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/siwe-auth.ts","../src/siwe-chain.ts","../src/siwe.ts","../src/storage.ts","../src/test-utils.ts","../src/token-utils.ts","../src/types.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["../src/chain-core.ts","../src/create-prividium-client.ts","../src/error-utils.ts","../src/errors.ts","../src/index.ts","../src/memory-storage.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/siwe-auth.ts","../src/siwe-chain.ts","../src/siwe.ts","../src/storage.ts","../src/test-utils.ts","../src/token-utils.ts","../src/types.ts","../src/selective-disclosure/actions.ts","../src/selective-disclosure/batch-info.ts","../src/selective-disclosure/disclosure-result.ts","../src/selective-disclosure/index.ts","../src/selective-disclosure/state-commitment.ts","../src/selective-disclosure/token-balance-disclosure.ts","../src/selective-disclosure/token-supply-disclosure.ts","../src/selective-disclosure/utils.ts","../src/selective-disclosure/verifiy-proofs.ts","../src/selective-disclosure/verify-disclosure.ts"],"version":"5.8.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prividium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"prividium": "bin/cli.js"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@fastify/http-proxy": "^11.3.0",
|
|
38
38
|
"@fastify/static": "^9.0.0",
|
|
39
39
|
"appdirsjs": "^1.2.7",
|
|
40
|
+
"blakejs": "^1.2.1",
|
|
40
41
|
"date-fns": "^4.1.0",
|
|
41
42
|
"fastify": "^5.7.2",
|
|
42
43
|
"fastify-type-provider-zod": "^6.1.0",
|