@collectorcrypt/vrf-client 0.1.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.
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pickCanonicalCommit = pickCanonicalCommit;
4
+ exports.verifyEndToEnd = verifyEndToEnd;
5
+ exports.verifyAuthorityCommitEndToEnd = verifyAuthorityCommitEndToEnd;
6
+ const ecvrf_1 = require("@collectorcrypt/ecvrf");
7
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
8
+ const addresses_1 = require("./addresses");
9
+ const constants_1 = require("./constants");
10
+ function bytesEqual(a, b) {
11
+ if (a.length !== b.length)
12
+ return false;
13
+ let diff = 0;
14
+ for (let i = 0; i < a.length; i++)
15
+ diff |= a[i] ^ b[i];
16
+ return diff === 0;
17
+ }
18
+ /**
19
+ * Resolve which committed event corresponds to a known-valid proof. Use this
20
+ * when fetching event-mode commits where the chain doesn't enforce
21
+ * one-commit-per-memo. Pass in all candidates from `fetchProofCommitEvents`
22
+ * plus the proof bytes the operator gave you; you get back the unique
23
+ * canonical row (or null if none match).
24
+ */
25
+ function pickCanonicalCommit(candidates, proof) {
26
+ const proofHash = (0, sha2_js_1.sha256)(proof);
27
+ const matching = candidates.filter((c) => bytesEqual(proofHash, c.proofHash));
28
+ return {
29
+ canonical: matching[0] ?? null,
30
+ candidates,
31
+ duplicateMemoEvents: candidates.length > 1,
32
+ multipleVerifying: matching.length > 1,
33
+ };
34
+ }
35
+ function verifyEndToEnd(input) {
36
+ const reasons = [];
37
+ const suite = input.suite ?? constants_1.SUITE_EDWARDS25519_SHA512_TAI;
38
+ const suiteSupported = suite === constants_1.SUITE_EDWARDS25519_SHA512_TAI;
39
+ if (!suiteSupported)
40
+ reasons.push(`unsupported-suite-${suite}`);
41
+ const ecvrfValid = suiteSupported && (0, ecvrf_1.verifyVRF)(input.pk, input.alpha, input.proof);
42
+ if (!ecvrfValid)
43
+ reasons.push("ecvrf-verify-failed");
44
+ const computedProofHash = (0, sha2_js_1.sha256)(input.proof);
45
+ const proofHashMatches = bytesEqual(computedProofHash, input.onChainCommit.proofHash);
46
+ if (!proofHashMatches) {
47
+ reasons.push(`proof-hash-mismatch (computed=${(0, ecvrf_1.bytesToHex)(computedProofHash)} onchain=${(0, ecvrf_1.bytesToHex)(input.onChainCommit.proofHash)})`);
48
+ }
49
+ const computedAlphaHash = (0, sha2_js_1.sha256)(input.alpha);
50
+ const alphaHashMatches = bytesEqual(computedAlphaHash, input.onChainCommit.alphaHash);
51
+ if (!alphaHashMatches)
52
+ reasons.push("alpha-hash-mismatch");
53
+ const memoBytes = typeof input.memo === "string"
54
+ ? new TextEncoder().encode(input.memo)
55
+ : input.memo;
56
+ const computedMemoHash = (0, sha2_js_1.sha256)(memoBytes);
57
+ const memoHashMatches = bytesEqual(computedMemoHash, input.onChainCommit.memoHash);
58
+ if (!memoHashMatches)
59
+ reasons.push("memo-hash-mismatch");
60
+ const valid = suiteSupported &&
61
+ ecvrfValid &&
62
+ proofHashMatches &&
63
+ alphaHashMatches &&
64
+ memoHashMatches;
65
+ const beta = ecvrfValid ? (0, ecvrf_1.vrfProofToHash)(input.proof) : null;
66
+ return {
67
+ valid,
68
+ ecvrfValid,
69
+ suiteSupported,
70
+ proofHashMatches,
71
+ alphaHashMatches,
72
+ memoHashMatches,
73
+ beta,
74
+ reasons,
75
+ };
76
+ }
77
+ function verifyAuthorityCommitEndToEnd(input) {
78
+ const base = verifyEndToEnd({
79
+ pk: input.authority.pk,
80
+ suite: input.authority.suite,
81
+ alpha: input.alpha,
82
+ proof: input.proof,
83
+ onChainCommit: input.onChainCommit,
84
+ memo: input.memo,
85
+ });
86
+ const reasons = [...base.reasons];
87
+ const authorityFrozen = input.authority.frozen;
88
+ if (!authorityFrozen)
89
+ reasons.push("authority-not-frozen");
90
+ const authorityNotRevoked = !input.authority.revoked;
91
+ if (!authorityNotRevoked)
92
+ reasons.push("authority-revoked");
93
+ const authorityOwnerMatches = input.expectedOwner
94
+ ? input.authority.owner.equals(input.expectedOwner)
95
+ : true;
96
+ if (!authorityOwnerMatches)
97
+ reasons.push("authority-owner-mismatch");
98
+ const authorityLabelMatches = input.expectedLabel
99
+ ? bytesEqual(input.authority.label, input.expectedLabel)
100
+ : true;
101
+ if (!authorityLabelMatches)
102
+ reasons.push("authority-label-mismatch");
103
+ // Always rederive the canonical authority address from (owner, label) and
104
+ // require the commit to point at it. This closes the prior footgun where a
105
+ // hand-built OnChainAuthority + missing expectedAuthorityAddress silently
106
+ // skipped the commit-to-authority binding.
107
+ const programId = input.programId ?? constants_1.CC_VRF_PROGRAM_ID;
108
+ const derivedAuthorityAddress = (0, addresses_1.deriveAuthorityAddress)(input.authority.owner, input.authority.label, programId);
109
+ const commitAuthority = input.onChainCommit.authority;
110
+ const commitAuthorityMatches = commitAuthority?.equals(derivedAuthorityAddress) === true;
111
+ if (!commitAuthorityMatches) {
112
+ reasons.push(commitAuthority
113
+ ? "commit-authority-mismatch"
114
+ : "commit-authority-missing");
115
+ }
116
+ // If the caller passed their own expectedAuthorityAddress, it must also
117
+ // match the derived one — otherwise the caller's expectation contradicts
118
+ // the (owner, label) they supplied.
119
+ const expectedAuthorityMatchesDerived = input.expectedAuthorityAddress
120
+ ? input.expectedAuthorityAddress.equals(derivedAuthorityAddress)
121
+ : true;
122
+ if (!expectedAuthorityMatchesDerived) {
123
+ reasons.push("expected-authority-address-mismatch");
124
+ }
125
+ const betaMatches = input.onChainBeta
126
+ ? base.beta !== null && bytesEqual(input.onChainBeta, base.beta)
127
+ : null;
128
+ if (betaMatches === false)
129
+ reasons.push("beta-mismatch");
130
+ const valid = base.valid &&
131
+ authorityFrozen &&
132
+ authorityNotRevoked &&
133
+ authorityOwnerMatches &&
134
+ authorityLabelMatches &&
135
+ commitAuthorityMatches &&
136
+ expectedAuthorityMatchesDerived &&
137
+ betaMatches !== false;
138
+ return {
139
+ ...base,
140
+ valid,
141
+ reasons,
142
+ authorityFrozen,
143
+ authorityNotRevoked,
144
+ authorityOwnerMatches,
145
+ authorityLabelMatches,
146
+ commitAuthorityMatches,
147
+ betaMatches,
148
+ };
149
+ }
150
+ //# sourceMappingURL=verifyEndToEnd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyEndToEnd.js","sourceRoot":"","sources":["../src/verifyEndToEnd.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@collectorcrypt/vrf-client",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for the cc-vrf on-chain VRF program (Solana + Light Protocol)",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src",
11
+ "README.md"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/collectorcrypt/cc-vrf.git",
16
+ "directory": "packages/vrf-client"
17
+ },
18
+ "homepage": "https://vrf.collectorcrypt.com",
19
+ "keywords": [
20
+ "vrf",
21
+ "ecvrf",
22
+ "solana",
23
+ "anchor",
24
+ "light-protocol",
25
+ "compressed-pda",
26
+ "verifiable-random-function",
27
+ "rfc-9381"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@coral-xyz/anchor": "^0.32.1",
34
+ "@lightprotocol/stateless.js": "^0.23.3",
35
+ "@noble/hashes": "^2.2.0",
36
+ "@solana/web3.js": "^1.98.4",
37
+ "@collectorcrypt/ecvrf": "0.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.7.0",
41
+ "typescript": "^6.0.3",
42
+ "vitest": "^4.1.6"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.json && node -e \"const fs=require('node:fs');fs.mkdirSync('dist/idl',{recursive:true});fs.copyFileSync('src/idl/cc_vrf.json','dist/idl/cc_vrf.json')\"",
46
+ "refresh:idl": "node -e \"const fs=require('node:fs');fs.mkdirSync('src/idl',{recursive:true});fs.copyFileSync('../../target/idl/cc_vrf.json','src/idl/cc_vrf.json');console.log('idl refreshed')\"",
47
+ "test": "vitest run"
48
+ }
49
+ }
@@ -0,0 +1,105 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ batchAddressTree,
4
+ deriveAddressSeedV2,
5
+ deriveAddressV2,
6
+ } from "@lightprotocol/stateless.js";
7
+ import { sha256 } from "@noble/hashes/sha2.js";
8
+ import {
9
+ AUTHORITY_SEED,
10
+ PROOF_COMMIT_SEED,
11
+ PROOF_COMMIT_WITH_BETA_SEED,
12
+ } from "./constants";
13
+
14
+ /**
15
+ * Derive the compressed-PDA address of a VrfAuthority. This is the address
16
+ * the Light state Merkle tree indexes — NOT a Solana account address.
17
+ *
18
+ * Seeds: ["vrf_authority", owner_pubkey, label_bytes]
19
+ */
20
+ export function deriveAuthorityAddress(
21
+ owner: PublicKey,
22
+ label: Uint8Array,
23
+ programId: PublicKey,
24
+ ): PublicKey {
25
+ if (label.length !== 32) {
26
+ throw new Error("label must be exactly 32 bytes");
27
+ }
28
+ const seed = deriveAddressSeedV2([AUTHORITY_SEED, owner.toBytes(), label]);
29
+ return deriveAddressV2(seed, new PublicKey(batchAddressTree), programId);
30
+ }
31
+
32
+ /**
33
+ * Derive the compressed-PDA address of a VrfProofCommit.
34
+ *
35
+ * Seeds: ["vrf_proof", authority_pubkey, memo_hash]
36
+ */
37
+ export function deriveProofCommitAddress(
38
+ authority: PublicKey,
39
+ memoHash: Uint8Array,
40
+ programId: PublicKey,
41
+ ): PublicKey {
42
+ if (memoHash.length !== 32) {
43
+ throw new Error("memoHash must be exactly 32 bytes");
44
+ }
45
+ const seed = deriveAddressSeedV2([
46
+ PROOF_COMMIT_SEED,
47
+ authority.toBytes(),
48
+ memoHash,
49
+ ]);
50
+ return deriveAddressV2(seed, new PublicKey(batchAddressTree), programId);
51
+ }
52
+
53
+ /**
54
+ * Derive the compressed-PDA address of a VrfProofCommitWithBeta. Uses a
55
+ * same seed prefix as regular VrfProofCommit records. This makes registry
56
+ * mode and registry+beta mutually exclusive for a given (authority, memo).
57
+ *
58
+ * Seeds: ["vrf_proof", authority_pubkey, memo_hash]
59
+ */
60
+ export function deriveProofCommitWithBetaAddress(
61
+ authority: PublicKey,
62
+ memoHash: Uint8Array,
63
+ programId: PublicKey,
64
+ ): PublicKey {
65
+ if (memoHash.length !== 32) {
66
+ throw new Error("memoHash must be exactly 32 bytes");
67
+ }
68
+ const seed = deriveAddressSeedV2([
69
+ PROOF_COMMIT_WITH_BETA_SEED,
70
+ authority.toBytes(),
71
+ memoHash,
72
+ ]);
73
+ return deriveAddressV2(seed, new PublicKey(batchAddressTree), programId);
74
+ }
75
+
76
+ /** Convenience: SHA-256 a UTF-8 memo string. */
77
+ export function memoHash(memo: string | Uint8Array): Uint8Array {
78
+ const input =
79
+ typeof memo === "string" ? new TextEncoder().encode(memo) : memo;
80
+ return sha256(input);
81
+ }
82
+
83
+ /** Convenience: SHA-256 the alpha bytes. */
84
+ export function alphaHash(alpha: Uint8Array): Uint8Array {
85
+ return sha256(alpha);
86
+ }
87
+
88
+ /** Convenience: SHA-256 the proof bytes. */
89
+ export function proofHash(proof: Uint8Array): Uint8Array {
90
+ return sha256(proof);
91
+ }
92
+
93
+ /**
94
+ * Pad a short utf-8 label to 32 bytes (right-padded with zeroes). Useful for
95
+ * human-readable labels like "gacha" or "lottery".
96
+ */
97
+ export function encodeLabel(label: string): Uint8Array {
98
+ const bytes = new TextEncoder().encode(label);
99
+ if (bytes.length > 32) {
100
+ throw new Error(`label "${label}" exceeds 32 bytes encoded`);
101
+ }
102
+ const padded = new Uint8Array(32);
103
+ padded.set(bytes, 0);
104
+ return padded;
105
+ }
@@ -0,0 +1,16 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+
3
+ /**
4
+ * Default mainnet program ID (renounced upgrade authority post-deploy).
5
+ * Override via `new VrfClient({ programId })` for devnet/local testing.
6
+ */
7
+ export const CC_VRF_PROGRAM_ID = new PublicKey(
8
+ "ccvrfu3fSpbnPLiUqdWAt85Zn9nq96ekwGTbHqGtdgQ",
9
+ );
10
+
11
+ export const AUTHORITY_SEED = new TextEncoder().encode("vrf_authority");
12
+ export const PROOF_COMMIT_SEED = new TextEncoder().encode("vrf_proof");
13
+ export const PROOF_COMMIT_WITH_BETA_SEED = PROOF_COMMIT_SEED;
14
+
15
+ /** RFC 9381 §7.5 IANA suite identifier. */
16
+ export const SUITE_EDWARDS25519_SHA512_TAI = 0x03;