prividium 0.15.1 → 0.17.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/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/selective-disclosure/account-properties.d.ts +12 -0
- package/dist/sdk/selective-disclosure/account-properties.js +113 -0
- package/dist/sdk/selective-disclosure/actions.d.ts +28 -0
- package/dist/sdk/selective-disclosure/actions.js +30 -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 +55 -0
- package/dist/sdk/selective-disclosure/disclosure-result.js +1 -0
- package/dist/sdk/selective-disclosure/index.d.ts +7 -0
- package/dist/sdk/selective-disclosure/index.js +7 -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/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 +40 -0
- package/dist/sdk/selective-disclosure/verify-disclosure.js +90 -0
- package/dist/tsconfig.sdk.tsbuildinfo +1 -1
- package/package.json +4 -3
|
@@ -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';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
2
|
+
import type { AccountDataDisclosureResult, AccountProperties } from './disclosure-result';
|
|
3
|
+
export declare function encodeAccountProperties(params: AccountProperties): Hex;
|
|
4
|
+
/**
|
|
5
|
+
* Compute the internal bytecode hash: blake2s(code || padding || artifacts)
|
|
6
|
+
* Returns { bytecodeHash, artifactsLen }
|
|
7
|
+
*/
|
|
8
|
+
export declare function computeInternalBytecodeHash(codeHex: Hex): {
|
|
9
|
+
bytecodeHash: Hex;
|
|
10
|
+
artifactsLen: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function verifyAccountPropertiesProof(disclosure: AccountDataDisclosureResult, l1Client: PublicClient, diamondAddress: Address, expectedBytecode?: Hex): Promise<boolean>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { bytesToHex, hexToBigInt, hexToBytes, keccak256, pad } from 'viem';
|
|
2
|
+
import { encodeBatchInfo } from './batch-info';
|
|
3
|
+
import { computeStateCommitment } from './state-commitment';
|
|
4
|
+
import { blake2s256 } from './utils';
|
|
5
|
+
import { calculateStateMerkleRoot } from './verifiy-proofs';
|
|
6
|
+
import { DIAMOND_ABI } from './verify-disclosure';
|
|
7
|
+
const PUSH1 = 0x60;
|
|
8
|
+
const PUSH32 = 0x7f;
|
|
9
|
+
const JUMPDEST = 0x5b;
|
|
10
|
+
const BYTECODE_ALIGNMENT = 8;
|
|
11
|
+
export function encodeAccountProperties(params) {
|
|
12
|
+
const buf = new Uint8Array(124);
|
|
13
|
+
const view = new DataView(buf.buffer);
|
|
14
|
+
view.setBigUint64(0, hexToBigInt(params.versioningData), false);
|
|
15
|
+
view.setBigUint64(8, hexToBigInt(params.nonce), false);
|
|
16
|
+
// Balance as 32 bytes big-endian
|
|
17
|
+
const balanceHex = hexToBigInt(params.balance).toString(16).padStart(64, '0');
|
|
18
|
+
for (let i = 0; i < 32; i++) {
|
|
19
|
+
buf[16 + i] = parseInt(balanceHex.slice(i * 2, i * 2 + 2), 16);
|
|
20
|
+
}
|
|
21
|
+
buf.set(hexToBytes(params.bytecodeHash), 48);
|
|
22
|
+
view.setUint32(80, params.unpaddedCodeLen, false);
|
|
23
|
+
view.setUint32(84, params.artifactsLen, false);
|
|
24
|
+
buf.set(hexToBytes(params.observableBytecodeHash), 88);
|
|
25
|
+
view.setUint32(120, params.observableBytecodeLen, false);
|
|
26
|
+
return bytesToHex(buf);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compute the internal bytecode hash: blake2s(code || padding || artifacts)
|
|
30
|
+
* Returns { bytecodeHash, artifactsLen }
|
|
31
|
+
*/
|
|
32
|
+
export function computeInternalBytecodeHash(codeHex) {
|
|
33
|
+
const code = hexToBytes(codeHex);
|
|
34
|
+
const paddingLen = computeBytecodePaddingLen(code.length);
|
|
35
|
+
const artifacts = computeJumpdestBitmap(code);
|
|
36
|
+
const fullLen = code.length + paddingLen + artifacts.length;
|
|
37
|
+
const buf = new Uint8Array(fullLen); // zero-initialized (padding is zeros)
|
|
38
|
+
buf.set(code, 0);
|
|
39
|
+
buf.set(artifacts, code.length + paddingLen);
|
|
40
|
+
return {
|
|
41
|
+
bytecodeHash: bytesToHex(blake2s256(buf)),
|
|
42
|
+
artifactsLen: artifacts.length
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const ACCOUNT_PROPERTIES_ADDRESS = '0x0000000000000000000000000000000000008003';
|
|
46
|
+
export async function verifyAccountPropertiesProof(disclosure, l1Client, diamondAddress, expectedBytecode) {
|
|
47
|
+
const isEoa = BigInt(disclosure.accountProperties.bytecodeHash) === 0n;
|
|
48
|
+
if (isEoa && expectedBytecode !== undefined && BigInt(expectedBytecode) !== 0n) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (!isEoa) {
|
|
52
|
+
if (expectedBytecode === undefined) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const { bytecodeHash: computedHash } = computeInternalBytecodeHash(expectedBytecode);
|
|
56
|
+
if (computedHash.toLowerCase() !== disclosure.accountProperties.bytecodeHash.toLowerCase()) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const { treeRoot, value: propertiesHash } = calculateStateMerkleRoot(disclosure.storageProof, ACCOUNT_PROPERTIES_ADDRESS, pad(disclosure.address));
|
|
61
|
+
const encodedProperties = encodeAccountProperties(disclosure.accountProperties);
|
|
62
|
+
const reconstructedHash = blake2s256(hexToBytes(encodedProperties));
|
|
63
|
+
if (bytesToHex(reconstructedHash).toLowerCase() !== propertiesHash?.toLowerCase()) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// Step 2: Compute state commitment from the tree root + preimage data.
|
|
67
|
+
const stateCommitment = computeStateCommitment(treeRoot, BigInt(disclosure.stateCommitmentPreimage.nextFreeSlot), BigInt(disclosure.stateCommitmentPreimage.blockNumber), disclosure.stateCommitmentPreimage.last256BlockHashesBlake, BigInt(disclosure.stateCommitmentPreimage.lastBlockTimestamp));
|
|
68
|
+
const batchInfoHex = encodeBatchInfo(BigInt(disclosure.batchNumber), stateCommitment, BigInt(disclosure.l1VerificationData.numberOfLayer1Txs), disclosure.l1VerificationData.priorityOperationsHash, disclosure.l1VerificationData.l2ToL1LogsRootHash, disclosure.l1VerificationData.commitment);
|
|
69
|
+
const batchInfoHash = keccak256(batchInfoHex);
|
|
70
|
+
// Step 4: Verify against L1 diamond proxy.
|
|
71
|
+
const storedBatchHash = await l1Client.readContract({
|
|
72
|
+
address: diamondAddress,
|
|
73
|
+
abi: DIAMOND_ABI,
|
|
74
|
+
functionName: 'storedBatchHash',
|
|
75
|
+
args: [BigInt(disclosure.batchNumber)]
|
|
76
|
+
});
|
|
77
|
+
return batchInfoHash.toLowerCase() === storedBatchHash.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the JUMPDEST bitmap matching the ZKsync OS evm_interpreter `analyze()`.
|
|
81
|
+
* The bitmap is stored as u64 words in little-endian byte order.
|
|
82
|
+
*/
|
|
83
|
+
function computeJumpdestBitmap(code) {
|
|
84
|
+
// Number of u64 words needed: ceil(code.length / 64)
|
|
85
|
+
const u64Count = Math.ceil(code.length / 64) || 0;
|
|
86
|
+
const bitmap = new Uint8Array(u64Count * 8); // zero-initialized
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < code.length) {
|
|
89
|
+
const op = code[i];
|
|
90
|
+
if (op === JUMPDEST) {
|
|
91
|
+
// Set bit at position i in the little-endian u64 bitmap.
|
|
92
|
+
// Word index (in u64 terms): Math.floor(i / 64)
|
|
93
|
+
// Bit within that u64: i % 64
|
|
94
|
+
// Byte within that u64 (little-endian): Math.floor((i % 64) / 8)
|
|
95
|
+
// Bit within that byte: (i % 64) % 8
|
|
96
|
+
const byteIndex = Math.floor(i / 64) * 8 + Math.floor((i % 64) / 8);
|
|
97
|
+
const bitIndex = (i % 64) % 8;
|
|
98
|
+
bitmap[byteIndex] |= 1 << bitIndex;
|
|
99
|
+
i += 1;
|
|
100
|
+
}
|
|
101
|
+
else if (op >= PUSH1 && op <= PUSH32) {
|
|
102
|
+
i += 1 + (op - PUSH1 + 1);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
i += 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return bitmap;
|
|
109
|
+
}
|
|
110
|
+
function computeBytecodePaddingLen(codeLen) {
|
|
111
|
+
const rem = codeLen % BYTECODE_ALIGNMENT;
|
|
112
|
+
return rem === 0 ? 0 : BYTECODE_ALIGNMENT - rem;
|
|
113
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Account, type Address, type BlockTag, type Chain, type Client, type Hex, type RpcSchema, type Transport } from 'viem';
|
|
2
|
+
import type { AccountDataDisclosureResult, EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
export type DisclosureBlockNumber = number | bigint | Hex | BlockTag;
|
|
4
|
+
export type SelectiveDisclosureActions = {
|
|
5
|
+
tokenSupplyDisclosure: (tokenAddress: Address, blockNumber: DisclosureBlockNumber) => Promise<EthCallDisclosureResult>;
|
|
6
|
+
tokenBalanceDisclosure: (tokenAddress: Address, holderAddress: Address, blockNumber: DisclosureBlockNumber) => Promise<EthCallDisclosureResult>;
|
|
7
|
+
accountDataDisclosure: (address: Address, blockNumber: DisclosureBlockNumber) => Promise<AccountDataDisclosureResult>;
|
|
8
|
+
};
|
|
9
|
+
export type TokenSupplyDisclosureRpc = {
|
|
10
|
+
Method: 'prividium_tokenSupplyDisclosure';
|
|
11
|
+
Parameters: [tokenAddress: Address, blockNumber: Hex | BlockTag];
|
|
12
|
+
ReturnType: EthCallDisclosureResult;
|
|
13
|
+
};
|
|
14
|
+
export type TokenBalanceDisclosureRpc = {
|
|
15
|
+
Method: 'prividium_tokenBalanceDisclosure';
|
|
16
|
+
Parameters: [tokenAddress: Address, holderAddress: Address, blockNumber: Hex | BlockTag];
|
|
17
|
+
ReturnType: EthCallDisclosureResult;
|
|
18
|
+
};
|
|
19
|
+
export type AccountDataDisclosureRpc = {
|
|
20
|
+
Method: 'prividium_accountDataDisclosure';
|
|
21
|
+
Parameters: [address: Address, blockNumber: Hex | BlockTag];
|
|
22
|
+
ReturnType: AccountDataDisclosureResult;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Viem action extender that adds Prividium selective-disclosure RPC methods
|
|
26
|
+
* to a client. Use with `client.extend(selectiveDisclosureActions)`.
|
|
27
|
+
*/
|
|
28
|
+
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,30 @@
|
|
|
1
|
+
import { numberToHex } from 'viem';
|
|
2
|
+
function encodeBlockNumber(block) {
|
|
3
|
+
return typeof block === 'string' ? block : numberToHex(block);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Viem action extender that adds Prividium selective-disclosure RPC methods
|
|
7
|
+
* to a client. Use with `client.extend(selectiveDisclosureActions)`.
|
|
8
|
+
*/
|
|
9
|
+
export function selectiveDisclosureActions(client) {
|
|
10
|
+
return {
|
|
11
|
+
async tokenSupplyDisclosure(tokenAddress, blockNumber) {
|
|
12
|
+
return client.request({
|
|
13
|
+
method: 'prividium_tokenSupplyDisclosure',
|
|
14
|
+
params: [tokenAddress, encodeBlockNumber(blockNumber)]
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
async tokenBalanceDisclosure(tokenAddress, holderAddress, blockNumber) {
|
|
18
|
+
return client.request({
|
|
19
|
+
method: 'prividium_tokenBalanceDisclosure',
|
|
20
|
+
params: [tokenAddress, holderAddress, encodeBlockNumber(blockNumber)]
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
async accountDataDisclosure(address, blockNumber) {
|
|
24
|
+
return client.request({
|
|
25
|
+
method: 'prividium_accountDataDisclosure',
|
|
26
|
+
params: [address, encodeBlockNumber(blockNumber)]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -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,55 @@
|
|
|
1
|
+
import type { Address, 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
|
+
};
|
|
37
|
+
export type AccountProperties = {
|
|
38
|
+
versioningData: Hex;
|
|
39
|
+
nonce: Hex;
|
|
40
|
+
balance: Hex;
|
|
41
|
+
bytecodeHash: Hex;
|
|
42
|
+
unpaddedCodeLen: number;
|
|
43
|
+
artifactsLen: number;
|
|
44
|
+
observableBytecodeHash: Hex;
|
|
45
|
+
observableBytecodeLen: number;
|
|
46
|
+
};
|
|
47
|
+
export type AccountDataDisclosureResult = {
|
|
48
|
+
accountProperties: AccountProperties;
|
|
49
|
+
address: Address;
|
|
50
|
+
bytecode: Hex;
|
|
51
|
+
batchNumber: number;
|
|
52
|
+
l1VerificationData: L1VerificationData;
|
|
53
|
+
stateCommitmentPreimage: StateCommitmentPreimage;
|
|
54
|
+
storageProof: StorageSlotProof;
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,14 @@
|
|
|
1
|
+
import blake 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 blake.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,40 @@
|
|
|
1
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
2
|
+
import type { EthCallDisclosureResult } from './disclosure-result';
|
|
3
|
+
export declare const DIAMOND_ABI: {
|
|
4
|
+
readonly name: "storedBatchHash";
|
|
5
|
+
readonly type: "function";
|
|
6
|
+
readonly stateMutability: "view";
|
|
7
|
+
readonly inputs: readonly [{
|
|
8
|
+
readonly type: "uint256";
|
|
9
|
+
readonly name: "_batchNumber";
|
|
10
|
+
}];
|
|
11
|
+
readonly outputs: readonly [{
|
|
12
|
+
readonly type: "bytes32";
|
|
13
|
+
}];
|
|
14
|
+
}[];
|
|
15
|
+
/**
|
|
16
|
+
* Verifies an ethCall disclosure response end-to-end.
|
|
17
|
+
*
|
|
18
|
+
* Verification steps:
|
|
19
|
+
* 1. Extracts Merkle roots from all storage proofs and checks they are consistent.
|
|
20
|
+
* 2. Computes the state commitment from the tree root and preimage data.
|
|
21
|
+
* 3. Encodes the batch info, hashes it, and compares against the L1 diamond proxy's `storedBatchHash`.
|
|
22
|
+
* 4. Replays the original call on `l2Client` with state overrides (proven storage + bytecodes)
|
|
23
|
+
* and checks the result matches the disclosed value.
|
|
24
|
+
*
|
|
25
|
+
* Throws if the disclosure data is internally inconsistent (e.g. no proofs, divergent roots,
|
|
26
|
+
* or missing bytecode for a contract referenced in the proofs).
|
|
27
|
+
*
|
|
28
|
+
* Returns `true` if every check passes, `false` otherwise.
|
|
29
|
+
*
|
|
30
|
+
* @param disclosure - The response from `prividium_tokenSupplyDisclosure` or `prividium_tokenBalanceDisclosure`.
|
|
31
|
+
* @param l1Client - A viem PublicClient connected to L1, used to read `storedBatchHash` from the diamond proxy.
|
|
32
|
+
* @param l2Client - A viem PublicClient connected to a local L2 node trusted by the caller. Used to
|
|
33
|
+
* replay the disclosed call with state overrides. This must be a node the caller trusts, not the
|
|
34
|
+
* Prividium proxy, so the replay cannot be manipulated.
|
|
35
|
+
* @param diamondAddress - The address of the L1 diamond proxy contract that stores batch commitments.
|
|
36
|
+
* @param contractBytecodes - A mapping from contract address to deployed bytecode (hex) for every
|
|
37
|
+
* contract referenced in the disclosure proofs. Used for the call replay with state overrides.
|
|
38
|
+
* @param batchNumber - The L1 batch number the disclosure was computed against.
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyEthCallDisclosure(disclosure: EthCallDisclosureResult, l1Client: PublicClient, l2Client: PublicClient, diamondAddress: Address, contractBytecodes: Record<Address, Hex>, batchNumber: number): Promise<boolean>;
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
export const DIAMOND_ABI = [
|
|
6
|
+
parseAbiItem('function storedBatchHash(uint256 _batchNumber) external view returns (bytes32)')
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Verifies an ethCall disclosure response end-to-end.
|
|
10
|
+
*
|
|
11
|
+
* Verification steps:
|
|
12
|
+
* 1. Extracts Merkle roots from all storage proofs and checks they are consistent.
|
|
13
|
+
* 2. Computes the state commitment from the tree root and preimage data.
|
|
14
|
+
* 3. Encodes the batch info, hashes it, and compares against the L1 diamond proxy's `storedBatchHash`.
|
|
15
|
+
* 4. Replays the original call on `l2Client` with state overrides (proven storage + bytecodes)
|
|
16
|
+
* and checks the result matches the disclosed value.
|
|
17
|
+
*
|
|
18
|
+
* Throws if the disclosure data is internally inconsistent (e.g. no proofs, divergent roots,
|
|
19
|
+
* or missing bytecode for a contract referenced in the proofs).
|
|
20
|
+
*
|
|
21
|
+
* Returns `true` if every check passes, `false` otherwise.
|
|
22
|
+
*
|
|
23
|
+
* @param disclosure - The response from `prividium_tokenSupplyDisclosure` or `prividium_tokenBalanceDisclosure`.
|
|
24
|
+
* @param l1Client - A viem PublicClient connected to L1, used to read `storedBatchHash` from the diamond proxy.
|
|
25
|
+
* @param l2Client - A viem PublicClient connected to a local L2 node trusted by the caller. Used to
|
|
26
|
+
* replay the disclosed call with state overrides. This must be a node the caller trusts, not the
|
|
27
|
+
* Prividium proxy, so the replay cannot be manipulated.
|
|
28
|
+
* @param diamondAddress - The address of the L1 diamond proxy contract that stores batch commitments.
|
|
29
|
+
* @param contractBytecodes - A mapping from contract address to deployed bytecode (hex) for every
|
|
30
|
+
* contract referenced in the disclosure proofs. Used for the call replay with state overrides.
|
|
31
|
+
* @param batchNumber - The L1 batch number the disclosure was computed against.
|
|
32
|
+
*/
|
|
33
|
+
export async function verifyEthCallDisclosure(disclosure, l1Client, l2Client, diamondAddress, contractBytecodes, batchNumber) {
|
|
34
|
+
// Step 1: Extract Merkle roots from all storage proofs and verify they're consistent.
|
|
35
|
+
const roots = [];
|
|
36
|
+
for (const proof of disclosure.proofs) {
|
|
37
|
+
for (const storageProof of proof.storageProofs) {
|
|
38
|
+
const { treeRoot } = calculateStateMerkleRoot(storageProof.proof, proof.address, storageProof.key);
|
|
39
|
+
roots.push(treeRoot);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (roots.length === 0) {
|
|
43
|
+
throw new Error('Inconsistent data: no proofs');
|
|
44
|
+
}
|
|
45
|
+
if (new Set(roots).size !== 1) {
|
|
46
|
+
throw new Error('Inconsistent data: multiple roots for same batch');
|
|
47
|
+
}
|
|
48
|
+
// Step 2: Compute state commitment from the tree root + preimage data.
|
|
49
|
+
const stateCommitment = computeStateCommitment(roots[0], BigInt(disclosure.stateCommitmentPreimage.nextFreeSlot), BigInt(disclosure.stateCommitmentPreimage.blockNumber), disclosure.stateCommitmentPreimage.last256BlockHashesBlake, BigInt(disclosure.stateCommitmentPreimage.lastBlockTimestamp));
|
|
50
|
+
// Step 3: Encode batch info and keccak256 hash it.
|
|
51
|
+
const batchInfoHex = encodeBatchInfo(BigInt(batchNumber), stateCommitment, BigInt(disclosure.l1VerificationData.numberOfLayer1Txs), disclosure.l1VerificationData.priorityOperationsHash, disclosure.l1VerificationData.l2ToL1LogsRootHash, disclosure.l1VerificationData.commitment);
|
|
52
|
+
const batchInfoHash = keccak256(batchInfoHex);
|
|
53
|
+
// Step 4: Verify against L1 diamond proxy.
|
|
54
|
+
const storedBatchHash = await l1Client.readContract({
|
|
55
|
+
address: diamondAddress,
|
|
56
|
+
abi: DIAMOND_ABI,
|
|
57
|
+
functionName: 'storedBatchHash',
|
|
58
|
+
args: [BigInt(batchNumber)]
|
|
59
|
+
});
|
|
60
|
+
if (batchInfoHash !== storedBatchHash) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
// Step 5: Replay the call on L2 with state overrides to verify the proven
|
|
64
|
+
// storage values produce the expected result.
|
|
65
|
+
const contractAddress = disclosure.proofs[0].address;
|
|
66
|
+
const stateOverride = disclosure.proofs.map((proof) => {
|
|
67
|
+
const address = proof.address.toLowerCase();
|
|
68
|
+
const code = contractBytecodes[address];
|
|
69
|
+
if (!code) {
|
|
70
|
+
throw new Error(`Missing bytecode for contract ${address}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
address,
|
|
74
|
+
code,
|
|
75
|
+
state: proof.storageProofs.map((sp) => ({
|
|
76
|
+
slot: sp.key,
|
|
77
|
+
value: sp.proof.type === 'existing' ? sp.proof.value : pad('0x0')
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const callResult = await l2Client.call({
|
|
82
|
+
to: contractAddress,
|
|
83
|
+
data: disclosure.callData,
|
|
84
|
+
stateOverride
|
|
85
|
+
});
|
|
86
|
+
if (!callResult.data) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return BigInt(callResult.data) === BigInt(disclosure.result);
|
|
90
|
+
}
|
|
@@ -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/account-properties.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/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.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"prividium": "bin/cli.js"
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@clack/prompts": "^0.11.0",
|
|
37
|
-
"@fastify/http-proxy": "^11.
|
|
37
|
+
"@fastify/http-proxy": "^11.4.4",
|
|
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
|
-
"fastify": "^5.
|
|
42
|
+
"fastify": "^5.8.5",
|
|
42
43
|
"fastify-type-provider-zod": "^6.1.0",
|
|
43
44
|
"kleur": "^4.1.5",
|
|
44
45
|
"open": "^10.1.0",
|