@zkclaw/credentials 1.0.0 → 1.0.1
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/package.json +5 -7
- package/src/circuit/Nargo.toml +9 -0
- package/src/circuit/src/main.nr +52 -0
- package/src/circuit/target/vk +0 -0
- package/src/circuit/target/vkey +0 -0
- package/src/index.ts +16 -0
- package/src/noir-lib/Nargo.toml +9 -0
- package/src/noir-lib/src/bytes/mod.nr +16 -0
- package/src/noir-lib/src/ecrecover/mod.nr +40 -0
- package/src/noir-lib/src/ecrecover/secp256k1.nr +87 -0
- package/src/noir-lib/src/lib.nr +4 -0
- package/src/noir-lib/src/proof/mod.nr +768 -0
- package/src/noir-lib/src/rlp/mod.nr +128 -0
- package/src/utils/circuit.ts +74 -0
- package/src/utils/index.ts +59 -0
- package/src/verifier.test.ts +163 -0
- package/src/verifier.ts +227 -0
- package/dist/index.js +0 -479
package/package.json
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkclaw/credentials",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/index.ts",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"import": "./
|
|
8
|
+
"import": "./src/index.ts",
|
|
9
|
+
"default": "./src/index.ts"
|
|
9
10
|
}
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
|
-
"
|
|
13
|
-
"src/circuit/target/*.json"
|
|
13
|
+
"src/**/*"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build:circuit": "cd src/circuit && nargo compile && bb write_vk -b target/main.json -o target/vkey",
|
|
17
|
-
"build": "tsup src/index.ts --format esm --target node18",
|
|
18
|
-
"prepublishOnly": "bun run build",
|
|
19
17
|
"test": "bun test",
|
|
20
18
|
"test:circuit": "cd src/circuit && nargo test"
|
|
21
19
|
},
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
use dep::lib::{ecrecover, proof};
|
|
2
|
+
|
|
3
|
+
/// Proves ownership of an ERC20 token balance without revealing the wallet address.
|
|
4
|
+
///
|
|
5
|
+
/// This circuit verifies:
|
|
6
|
+
/// 1. The signer owns a wallet (via ECDSA signature verification)
|
|
7
|
+
/// 2. That wallet has at least `verified_balance` tokens at the given block
|
|
8
|
+
///
|
|
9
|
+
/// Used specifically for $ANON token on Base chain.
|
|
10
|
+
fn main(
|
|
11
|
+
// Address Data (proves wallet ownership)
|
|
12
|
+
signature: [u8; 64],
|
|
13
|
+
message_hash: [u8; 32],
|
|
14
|
+
pub_key_x: [u8; 32],
|
|
15
|
+
pub_key_y: [u8; 32],
|
|
16
|
+
// Storage Proof (proves balance)
|
|
17
|
+
// Array sizes tuned for ~108k holders (max depth 6, max node 1066 bytes)
|
|
18
|
+
// Will need to increase if holder count exceeds ~1M
|
|
19
|
+
storage_hash: [u8; 32],
|
|
20
|
+
storage_nodes: [[u8; 1080]; 5],
|
|
21
|
+
storage_leaf: [u8; 120],
|
|
22
|
+
storage_depth: u32,
|
|
23
|
+
storage_value: Field,
|
|
24
|
+
// Verification Data
|
|
25
|
+
chain_id: Field,
|
|
26
|
+
block_number: Field,
|
|
27
|
+
token_address: Field,
|
|
28
|
+
balance_slot: Field,
|
|
29
|
+
// Balance Data
|
|
30
|
+
verified_balance: Field,
|
|
31
|
+
) -> pub (Field, Field, Field, Field, Field, [u8; 32]) {
|
|
32
|
+
let balance_slot_bytes: [u8; 32] = balance_slot.to_be_bytes();
|
|
33
|
+
let address: [u8; 32] =
|
|
34
|
+
ecrecover::ecrecover(pub_key_x, pub_key_y, signature, message_hash).to_be_bytes();
|
|
35
|
+
|
|
36
|
+
let key = proof::get_storage_key([address, balance_slot_bytes]);
|
|
37
|
+
let value = proof::get_storage_value(storage_value);
|
|
38
|
+
|
|
39
|
+
proof::verify(
|
|
40
|
+
key,
|
|
41
|
+
value,
|
|
42
|
+
storage_hash,
|
|
43
|
+
storage_nodes,
|
|
44
|
+
storage_leaf,
|
|
45
|
+
storage_depth,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Ensure verified_balance <= actual balance (prevents over-claiming)
|
|
49
|
+
assert(!storage_value.lt(verified_balance), "Attempt to verify too high balance");
|
|
50
|
+
|
|
51
|
+
(verified_balance, chain_id, block_number, token_address, balance_slot, storage_hash)
|
|
52
|
+
}
|
|
Binary file
|
|
Binary file
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Set HOME to /tmp for serverless environments (Vercel) where Barretenberg needs write access
|
|
2
|
+
if (typeof process !== 'undefined' && process.env && process.env.VERCEL) {
|
|
3
|
+
process.env.HOME = '/tmp'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
AnonBalanceVerifier,
|
|
8
|
+
getVerifier,
|
|
9
|
+
ZKCLAW_TOKEN,
|
|
10
|
+
ANON_TOKEN, // Legacy alias
|
|
11
|
+
BALANCE_THRESHOLDS,
|
|
12
|
+
type ProofData,
|
|
13
|
+
type CredentialData,
|
|
14
|
+
type BuildInputResult,
|
|
15
|
+
type GenerateProofInput,
|
|
16
|
+
} from './verifier'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pub fn bytes_as_nibbles<let N: u32>(bytes: [u8; N]) -> [u8; N * 2] {
|
|
2
|
+
let mut nibbles = [0; N * 2];
|
|
3
|
+
for i in 0..N {
|
|
4
|
+
let nibs = byte_to_nibbles(bytes[i]);
|
|
5
|
+
nibbles[i * 2] = nibs.0;
|
|
6
|
+
nibbles[i * 2 + 1] = nibs.1;
|
|
7
|
+
}
|
|
8
|
+
nibbles
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub fn byte_to_nibbles(b: u8) -> (u8, u8) {
|
|
12
|
+
let upper = b >> 4;
|
|
13
|
+
let lower = b - 16 * upper;
|
|
14
|
+
|
|
15
|
+
(upper, lower)
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
mod secp256k1;
|
|
2
|
+
|
|
3
|
+
pub fn ecrecover(
|
|
4
|
+
pub_key_x: [u8; 32],
|
|
5
|
+
pub_key_y: [u8; 32],
|
|
6
|
+
signature: [u8; 64], // clip v value
|
|
7
|
+
hashed_message: [u8; 32],
|
|
8
|
+
) -> Field {
|
|
9
|
+
let key = secp256k1::PubKey::from_xy(pub_key_x, pub_key_y);
|
|
10
|
+
|
|
11
|
+
assert(key.verify_sig(signature, hashed_message));
|
|
12
|
+
let addr = key.to_eth_address();
|
|
13
|
+
|
|
14
|
+
addr
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[test]
|
|
18
|
+
fn test_ecrecover() {
|
|
19
|
+
let pub_key_x = [
|
|
20
|
+
131, 24, 83, 91, 84, 16, 93, 74, 122, 174, 96, 192, 143, 196, 95, 150, 135, 24, 27, 79, 223,
|
|
21
|
+
198, 37, 189, 26, 117, 63, 167, 57, 127, 237, 117,
|
|
22
|
+
];
|
|
23
|
+
let pub_key_y = [
|
|
24
|
+
53, 71, 241, 28, 168, 105, 102, 70, 242, 243, 172, 176, 142, 49, 1, 106, 250, 194, 62, 99,
|
|
25
|
+
12, 93, 17, 245, 159, 97, 254, 245, 123, 13, 42, 165,
|
|
26
|
+
];
|
|
27
|
+
let signature = [
|
|
28
|
+
57, 17, 112, 239, 241, 30, 64, 157, 170, 50, 85, 145, 156, 69, 226, 85, 147, 164, 10, 82,
|
|
29
|
+
71, 93, 42, 132, 200, 220, 161, 255, 95, 241, 211, 141, 81, 7, 150, 25, 25, 27, 162, 213,
|
|
30
|
+
80, 61, 12, 170, 50, 4, 154, 203, 252, 229, 119, 29, 202, 153, 50, 25, 126, 145, 245, 23,
|
|
31
|
+
136, 75, 29, 177,
|
|
32
|
+
];
|
|
33
|
+
let hashed_message = [
|
|
34
|
+
13, 82, 120, 60, 76, 186, 215, 235, 175, 126, 185, 67, 252, 100, 143, 82, 130, 165, 32, 112,
|
|
35
|
+
68, 47, 193, 141, 141, 209, 109, 219, 47, 203, 175, 102,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
let addr = ecrecover(pub_key_x, pub_key_y, signature, hashed_message);
|
|
39
|
+
assert(addr == 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266);
|
|
40
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
use dep::std;
|
|
2
|
+
|
|
3
|
+
use dep::array_helpers;
|
|
4
|
+
|
|
5
|
+
struct PubKey {
|
|
6
|
+
pub_x: [u8; 32],
|
|
7
|
+
pub_y: [u8; 32],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn split_uncompressed_pub_key(pub_key: [u8; 65]) -> ([u8; 32], [u8; 32]) {
|
|
11
|
+
let mut pub_key_x: [u8; 32] = [0; 32];
|
|
12
|
+
let mut pub_key_y: [u8; 32] = [0; 32];
|
|
13
|
+
|
|
14
|
+
for i in 0..32 {
|
|
15
|
+
pub_key_x[i] = pub_key[i + 1];
|
|
16
|
+
pub_key_y[i] = pub_key[i + 32 + 1];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
(pub_key_x, pub_key_y)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl PubKey {
|
|
23
|
+
fn from_xy(pub_x: [u8; 32], pub_y: [u8; 32]) -> PubKey {
|
|
24
|
+
PubKey { pub_x, pub_y }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn from_unified(pub_key: [u8; 64]) -> PubKey {
|
|
28
|
+
let (key_x, key_y) = array_helpers::split_u8_64(pub_key);
|
|
29
|
+
|
|
30
|
+
PubKey { pub_x: key_x, pub_y: key_y }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn from_uncompressed(pub_key: [u8; 65]) -> PubKey {
|
|
34
|
+
assert(pub_key[0] == 0x04);
|
|
35
|
+
let (key_x, key_y) = split_uncompressed_pub_key(pub_key);
|
|
36
|
+
|
|
37
|
+
PubKey { pub_x: key_x, pub_y: key_y }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn verify_sig(self, signature: [u8; 64], hashed_message: [u8; 32]) -> bool {
|
|
41
|
+
std::ecdsa_secp256k1::verify_signature(self.pub_x, self.pub_y, signature, hashed_message)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn to_eth_address(self) -> Field {
|
|
45
|
+
let pub_key = array_helpers::u8_32_to_u8_64(self.pub_x, self.pub_y);
|
|
46
|
+
let hashed_pub_key = std::hash::keccak256(pub_key, 64);
|
|
47
|
+
|
|
48
|
+
let mut addr: Field = 0;
|
|
49
|
+
for i in 0..20 {
|
|
50
|
+
// shift left by 8 and add the new value
|
|
51
|
+
addr = (addr * 256) + hashed_pub_key[i + 12] as Field;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
addr
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn ecrecover(self, signature: [u8; 64], hashed_message: [u8; 32]) -> Field {
|
|
58
|
+
assert(self.verify_sig(signature, hashed_message));
|
|
59
|
+
|
|
60
|
+
self.to_eth_address()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn test_ecrecover_via_key() {
|
|
66
|
+
let pub_key_x = [
|
|
67
|
+
131, 24, 83, 91, 84, 16, 93, 74, 122, 174, 96, 192, 143, 196, 95, 150, 135, 24, 27, 79, 223,
|
|
68
|
+
198, 37, 189, 26, 117, 63, 167, 57, 127, 237, 117,
|
|
69
|
+
];
|
|
70
|
+
let pub_key_y = [
|
|
71
|
+
53, 71, 241, 28, 168, 105, 102, 70, 242, 243, 172, 176, 142, 49, 1, 106, 250, 194, 62, 99,
|
|
72
|
+
12, 93, 17, 245, 159, 97, 254, 245, 123, 13, 42, 165,
|
|
73
|
+
];
|
|
74
|
+
let signature = [
|
|
75
|
+
57, 17, 112, 239, 241, 30, 64, 157, 170, 50, 85, 145, 156, 69, 226, 85, 147, 164, 10, 82,
|
|
76
|
+
71, 93, 42, 132, 200, 220, 161, 255, 95, 241, 211, 141, 81, 7, 150, 25, 25, 27, 162, 213,
|
|
77
|
+
80, 61, 12, 170, 50, 4, 154, 203, 252, 229, 119, 29, 202, 153, 50, 25, 126, 145, 245, 23,
|
|
78
|
+
136, 75, 29, 177,
|
|
79
|
+
];
|
|
80
|
+
let hashed_message = [
|
|
81
|
+
13, 82, 120, 60, 76, 186, 215, 235, 175, 126, 185, 67, 252, 100, 143, 82, 130, 165, 32, 112,
|
|
82
|
+
68, 47, 193, 141, 141, 209, 109, 219, 47, 203, 175, 102,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
let key = PubKey::from_xy(pub_key_x, pub_key_y);
|
|
86
|
+
assert(key.ecrecover(signature, hashed_message) == 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266);
|
|
87
|
+
}
|