@verify-network/protocol 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/dist/domain-CXJsoNRU.d.ts +9 -0
- package/dist/ecvrf.d.ts +69 -0
- package/dist/ecvrf.js +174 -0
- package/dist/event.d.ts +101 -0
- package/dist/event.js +53 -0
- package/dist/fee.d.ts +142 -0
- package/dist/fee.js +142 -0
- package/dist/fulfillment.d.ts +32 -0
- package/dist/fulfillment.js +28 -0
- package/dist/index.d.ts +708 -0
- package/dist/index.js +872 -0
- package/dist/request.d.ts +60 -0
- package/dist/request.js +61 -0
- package/package.json +56 -4
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Verify Network
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Address } from "viem";
|
|
2
|
+
//#region src/domain.d.ts
|
|
3
|
+
/** One router deployment: what request ids and fulfillment digests are bound to. */
|
|
4
|
+
type RouterDomain = {
|
|
5
|
+
readonly chainId: bigint | number;
|
|
6
|
+
readonly router: Address;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { RouterDomain as t };
|
package/dist/ecvrf.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Hex } from "viem";
|
|
2
|
+
//#region src/ecvrf.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* ECVRF-SECP256K1-SHA256-TAI, the RFC 9381 cipher suite the router's `ECVRFVerifier` checks,
|
|
5
|
+
* on `@noble/curves` and `@noble/hashes`. Hash-to-curve, the challenge, the proof encoding,
|
|
6
|
+
* and the randomness derivation mirror `VRF.sol` so the enclave and the router agree byte for
|
|
7
|
+
* byte; the nonce is RFC 6979, as RFC 9381 prescribes for the suite. Every function is pure and
|
|
8
|
+
* synchronous, so it runs unchanged on a `node:worker_threads` worker, under Node and Bun.
|
|
9
|
+
*/
|
|
10
|
+
/** A proof as the router takes it: `[gammaX, gammaY, c, s]`. */
|
|
11
|
+
export type Proof = readonly [gammaX: bigint, gammaY: bigint, c: bigint, s: bigint];
|
|
12
|
+
/** An enclave key as the router stores it: the uncompressed point's `[x, y]`. */
|
|
13
|
+
export type PublicKey = readonly [x: bigint, y: bigint];
|
|
14
|
+
/**
|
|
15
|
+
* `VRF.HASH_TO_CURVE_MAX_ITERATIONS`: the try-and-increment attempts the on-chain verifier makes
|
|
16
|
+
* before declaring an alpha unfulfillable (probability 2^-50 per request).
|
|
17
|
+
*/
|
|
18
|
+
export declare const HASH_TO_CURVE_MAX_ITERATIONS = 50;
|
|
19
|
+
/** What a fulfiller submits for an alpha that fails the hash-to-curve check. */
|
|
20
|
+
export declare const SENTINEL_PROOF: Proof;
|
|
21
|
+
/** The router recognises the sentinel by its gamma alone. */
|
|
22
|
+
export declare const isSentinel: ([gammaX, gammaY]: Proof) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The `[x, y]` point the router stores, from the key viem derives (`0x04 || x || y`). Throws
|
|
25
|
+
* when the key is not on the curve.
|
|
26
|
+
*/
|
|
27
|
+
export declare const publicKeyPoint: (publicKey: Hex) => PublicKey;
|
|
28
|
+
/**
|
|
29
|
+
* Proves `alpha` (the request id) under the enclave key. Deterministic: RFC 6979 fixes the nonce.
|
|
30
|
+
* Throws when `alpha` has no curve point within the on-chain cap; the fulfiller submits the
|
|
31
|
+
* sentinel for such a request (see `isFulfillable`).
|
|
32
|
+
*/
|
|
33
|
+
export declare const prove: ({ privateKey, alpha }: {
|
|
34
|
+
privateKey: Hex;
|
|
35
|
+
alpha: Hex;
|
|
36
|
+
}) => Proof;
|
|
37
|
+
/**
|
|
38
|
+
* Whether `proof` proves `alpha` under `publicKey`, as `ECVRFVerifier.verify` decides: gamma must
|
|
39
|
+
* be a curve point with the `y` as submitted (the router multiplies it as given), `c` must fit 16
|
|
40
|
+
* bytes, `s` must be below the group order, and `alpha` must hash to a point within the cap.
|
|
41
|
+
*/
|
|
42
|
+
export declare const verify: ({ publicKey, proof, alpha }: {
|
|
43
|
+
publicKey: PublicKey;
|
|
44
|
+
proof: Proof;
|
|
45
|
+
alpha: Hex;
|
|
46
|
+
}) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The 81-byte RFC 9381 proof string: compressed gamma, `c` in 16 bytes, `s` in 32. Throws when
|
|
49
|
+
* a scalar does not fit.
|
|
50
|
+
*/
|
|
51
|
+
export declare const encodeProof: ([gammaX, gammaY, c, s]: Proof) => Hex;
|
|
52
|
+
/** The router's four limbs from an RFC 9381 proof string. Throws when the string is malformed. */
|
|
53
|
+
export declare const decodeProof: (encoded: Hex) => Proof;
|
|
54
|
+
/**
|
|
55
|
+
* The randomness a proof yields, as `VRF.gammaToHash` derives it: sha256 of the suite byte,
|
|
56
|
+
* `0x03`, the compressed gamma, and a zero byte. Zero for the sentinel, as the router emits it.
|
|
57
|
+
*/
|
|
58
|
+
export declare const deriveRandomness: (proof: Proof) => Hex;
|
|
59
|
+
/** What the enclave signs over: `keccak256(abi.encode(proof))` in the router. */
|
|
60
|
+
export declare const proofCommitment: (proof: Proof) => Hex;
|
|
61
|
+
/**
|
|
62
|
+
* The try-and-increment attempts `VRF.tryHashToPoint` needs to hash `alpha` to a point for
|
|
63
|
+
* `publicKey`, or `null` when none is found within `maxIterations`: such a request cannot be
|
|
64
|
+
* proven and is closed with the sentinel.
|
|
65
|
+
*/
|
|
66
|
+
export declare const hashToCurveIterations: (publicKey: PublicKey, alpha: Hex, maxIterations?: number) => number | null;
|
|
67
|
+
/** Whether the on-chain verifier can hash `alpha` to a point for `publicKey`. */
|
|
68
|
+
export declare const isFulfillable: (publicKey: PublicKey, alpha: Hex, maxIterations?: number) => boolean;
|
|
69
|
+
//#endregion
|
package/dist/ecvrf.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { bytesToHex, encodeAbiParameters, hexToBytes, keccak256, zeroHash } from "viem";
|
|
2
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
3
|
+
import { bytesToNumberBE, concatBytes, createHmacDrbg, numberToBytesBE } from "@noble/curves/utils.js";
|
|
4
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
5
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
|
+
//#region src/ecvrf.ts
|
|
7
|
+
/**
|
|
8
|
+
* `VRF.HASH_TO_CURVE_MAX_ITERATIONS`: the try-and-increment attempts the on-chain verifier makes
|
|
9
|
+
* before declaring an alpha unfulfillable (probability 2^-50 per request).
|
|
10
|
+
*/
|
|
11
|
+
const HASH_TO_CURVE_MAX_ITERATIONS = 50;
|
|
12
|
+
/** What a fulfiller submits for an alpha that fails the hash-to-curve check. */
|
|
13
|
+
const SENTINEL_PROOF = [
|
|
14
|
+
0n,
|
|
15
|
+
0n,
|
|
16
|
+
0n,
|
|
17
|
+
0n
|
|
18
|
+
];
|
|
19
|
+
/** The router recognises the sentinel by its gamma alone. */
|
|
20
|
+
const isSentinel = ([gammaX, gammaY]) => gammaX === 0n && gammaY === 0n;
|
|
21
|
+
const { Point } = secp256k1;
|
|
22
|
+
const G = Point.BASE;
|
|
23
|
+
/** The group order, RFC 9381's `q`. */
|
|
24
|
+
const Q = Point.Fn.ORDER;
|
|
25
|
+
/** The suite string; the byte after it separates the three hashes the suite takes. */
|
|
26
|
+
const SUITE = 254;
|
|
27
|
+
/** RFC 9381 sizes for the suite: the challenge `c` is 16 bytes, the proof string 33 + 16 + 32. */
|
|
28
|
+
const C_BYTES = 16;
|
|
29
|
+
const C_LIMIT = 1n << 128n;
|
|
30
|
+
const PROOF_BYTES = 81;
|
|
31
|
+
/** The curve point at `[x, y]`. Throws when there is none, including for the sentinel. */
|
|
32
|
+
const point = ([x, y]) => {
|
|
33
|
+
const p = Point.fromAffine({
|
|
34
|
+
x,
|
|
35
|
+
y
|
|
36
|
+
});
|
|
37
|
+
p.assertValidity();
|
|
38
|
+
return p;
|
|
39
|
+
};
|
|
40
|
+
const limbs = (p) => {
|
|
41
|
+
const { x, y } = p.toAffine();
|
|
42
|
+
return [x, y];
|
|
43
|
+
};
|
|
44
|
+
/** `VRF.encodePoint`: SEC 1 compressed, the parity prefix then `x`, over the limbs as given. */
|
|
45
|
+
const encodePoint = (x, y) => concatBytes(Uint8Array.of(y % 2n === 0n ? 2 : 3), numberToBytesBE(x, 32));
|
|
46
|
+
const compressed = (p) => encodePoint(...limbs(p));
|
|
47
|
+
/**
|
|
48
|
+
* The `[x, y]` point the router stores, from the key viem derives (`0x04 || x || y`). Throws
|
|
49
|
+
* when the key is not on the curve.
|
|
50
|
+
*/
|
|
51
|
+
const publicKeyPoint = (publicKey) => limbs(Point.fromBytes(hexToBytes(publicKey)));
|
|
52
|
+
/**
|
|
53
|
+
* `VRF.tryHashToPoint`: try-and-increment over
|
|
54
|
+
* `sha256(0xfe || 0x01 || compressed key || alpha || counter || 0x00)`, reading each digest as
|
|
55
|
+
* the `x` of an even-`y` point; a digest of zero or at least the field prime yields none, as
|
|
56
|
+
* `EllipticCurve.isOnCurve` decides. The point and the attempts it took, or `null` past the cap.
|
|
57
|
+
*/
|
|
58
|
+
const hashToCurve = (publicKey, alpha, maxIterations) => {
|
|
59
|
+
const prefix = concatBytes(Uint8Array.of(SUITE, 1), encodePoint(...publicKey), alpha);
|
|
60
|
+
for (let counter = 0; counter < maxIterations; counter++) {
|
|
61
|
+
const digest = sha256(concatBytes(prefix, Uint8Array.of(counter, 0)));
|
|
62
|
+
try {
|
|
63
|
+
return {
|
|
64
|
+
point: Point.fromBytes(concatBytes(Uint8Array.of(2), digest)),
|
|
65
|
+
iterations: counter + 1
|
|
66
|
+
};
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* `VRF.hashPoints`: the first 16 bytes of `sha256(0xfe || 0x02 || H || Gamma || U || V || 0x00)`,
|
|
73
|
+
* the points compressed.
|
|
74
|
+
*/
|
|
75
|
+
const challenge = (points) => bytesToNumberBE(sha256(concatBytes(Uint8Array.of(SUITE, 2), ...points.map(compressed), Uint8Array.of(0))).subarray(0, C_BYTES));
|
|
76
|
+
const drbg = createHmacDrbg(32, 32, (key, message) => hmac(sha256, key, message));
|
|
77
|
+
/**
|
|
78
|
+
* RFC 9381 §5.4.2.1: the nonce RFC 6979 §3.2 derives from the secret scalar and `sha256` of the
|
|
79
|
+
* compressed `H`, so a proof is a function of the key and alpha alone.
|
|
80
|
+
*/
|
|
81
|
+
const nonce = (x, h) => {
|
|
82
|
+
const h1 = bytesToNumberBE(sha256(compressed(h))) % Q;
|
|
83
|
+
return drbg(concatBytes(numberToBytesBE(x, 32), numberToBytesBE(h1, 32)), (candidate) => {
|
|
84
|
+
const k = bytesToNumberBE(candidate);
|
|
85
|
+
return k > 0n && k < Q ? k : void 0;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Proves `alpha` (the request id) under the enclave key. Deterministic: RFC 6979 fixes the nonce.
|
|
90
|
+
* Throws when `alpha` has no curve point within the on-chain cap; the fulfiller submits the
|
|
91
|
+
* sentinel for such a request (see `isFulfillable`).
|
|
92
|
+
*/
|
|
93
|
+
const prove = ({ privateKey, alpha }) => {
|
|
94
|
+
const x = bytesToNumberBE(hexToBytes(privateKey));
|
|
95
|
+
const publicKey = G.multiply(x);
|
|
96
|
+
const hashed = hashToCurve(limbs(publicKey), hexToBytes(alpha), 50);
|
|
97
|
+
if (hashed === null) throw new Error("ECVRF alpha has no curve point within the on-chain cap");
|
|
98
|
+
const h = hashed.point;
|
|
99
|
+
const gamma = h.multiply(x);
|
|
100
|
+
const k = nonce(x, h);
|
|
101
|
+
const c = challenge([
|
|
102
|
+
h,
|
|
103
|
+
gamma,
|
|
104
|
+
G.multiply(k),
|
|
105
|
+
h.multiply(k)
|
|
106
|
+
]);
|
|
107
|
+
return [
|
|
108
|
+
...limbs(gamma),
|
|
109
|
+
c,
|
|
110
|
+
(k + c * x) % Q
|
|
111
|
+
];
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Whether `proof` proves `alpha` under `publicKey`, as `ECVRFVerifier.verify` decides: gamma must
|
|
115
|
+
* be a curve point with the `y` as submitted (the router multiplies it as given), `c` must fit 16
|
|
116
|
+
* bytes, `s` must be below the group order, and `alpha` must hash to a point within the cap.
|
|
117
|
+
*/
|
|
118
|
+
const verify = ({ publicKey, proof, alpha }) => {
|
|
119
|
+
const [gammaX, gammaY, c, s] = proof;
|
|
120
|
+
if (c >= C_LIMIT || s >= Q) return false;
|
|
121
|
+
try {
|
|
122
|
+
const y = point(publicKey);
|
|
123
|
+
const gamma = point([gammaX, gammaY]);
|
|
124
|
+
const hashed = hashToCurve(publicKey, hexToBytes(alpha), 50);
|
|
125
|
+
if (hashed === null) return false;
|
|
126
|
+
const h = hashed.point;
|
|
127
|
+
const u = G.mulAddUnsafe(s, y.negate(), c);
|
|
128
|
+
const v = h.mulAddUnsafe(s, gamma.negate(), c);
|
|
129
|
+
return challenge([
|
|
130
|
+
h,
|
|
131
|
+
gamma,
|
|
132
|
+
u,
|
|
133
|
+
v
|
|
134
|
+
]) === c;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* The 81-byte RFC 9381 proof string: compressed gamma, `c` in 16 bytes, `s` in 32. Throws when
|
|
141
|
+
* a scalar does not fit.
|
|
142
|
+
*/
|
|
143
|
+
const encodeProof = ([gammaX, gammaY, c, s]) => bytesToHex(concatBytes(encodePoint(gammaX, gammaY), numberToBytesBE(c, C_BYTES), numberToBytesBE(s, 32)));
|
|
144
|
+
/** The router's four limbs from an RFC 9381 proof string. Throws when the string is malformed. */
|
|
145
|
+
const decodeProof = (encoded) => {
|
|
146
|
+
const proof = hexToBytes(encoded);
|
|
147
|
+
if (proof.length !== PROOF_BYTES) throw new Error("ECVRF proof must be 81 bytes");
|
|
148
|
+
const gamma = Point.fromBytes(proof.subarray(0, 33));
|
|
149
|
+
const c = bytesToNumberBE(proof.subarray(33, 49));
|
|
150
|
+
const s = bytesToNumberBE(proof.subarray(49));
|
|
151
|
+
if (s >= Q) throw new Error("ECVRF proof scalar s must be below the group order");
|
|
152
|
+
return [
|
|
153
|
+
...limbs(gamma),
|
|
154
|
+
c,
|
|
155
|
+
s
|
|
156
|
+
];
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* The randomness a proof yields, as `VRF.gammaToHash` derives it: sha256 of the suite byte,
|
|
160
|
+
* `0x03`, the compressed gamma, and a zero byte. Zero for the sentinel, as the router emits it.
|
|
161
|
+
*/
|
|
162
|
+
const deriveRandomness = (proof) => isSentinel(proof) ? zeroHash : bytesToHex(sha256(concatBytes(Uint8Array.of(SUITE, 3), encodePoint(proof[0], proof[1]), Uint8Array.of(0))));
|
|
163
|
+
/** What the enclave signs over: `keccak256(abi.encode(proof))` in the router. */
|
|
164
|
+
const proofCommitment = (proof) => keccak256(encodeAbiParameters([{ type: "uint256[4]" }], [proof]));
|
|
165
|
+
/**
|
|
166
|
+
* The try-and-increment attempts `VRF.tryHashToPoint` needs to hash `alpha` to a point for
|
|
167
|
+
* `publicKey`, or `null` when none is found within `maxIterations`: such a request cannot be
|
|
168
|
+
* proven and is closed with the sentinel.
|
|
169
|
+
*/
|
|
170
|
+
const hashToCurveIterations = (publicKey, alpha, maxIterations = 50) => hashToCurve(publicKey, hexToBytes(alpha), maxIterations)?.iterations ?? null;
|
|
171
|
+
/** Whether the on-chain verifier can hash `alpha` to a point for `publicKey`. */
|
|
172
|
+
const isFulfillable = (publicKey, alpha, maxIterations = 50) => hashToCurveIterations(publicKey, alpha, maxIterations) !== null;
|
|
173
|
+
//#endregion
|
|
174
|
+
export { HASH_TO_CURVE_MAX_ITERATIONS, SENTINEL_PROOF, decodeProof, deriveRandomness, encodeProof, hashToCurveIterations, isFulfillable, isSentinel, proofCommitment, prove, publicKeyPoint, verify };
|
package/dist/event.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Proof } from "./ecvrf.js";
|
|
2
|
+
import { Request } from "./request.js";
|
|
3
|
+
import { Hex, Log } from "viem";
|
|
4
|
+
//#region src/event.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `VrfFacet.RandomnessRequested`: the id, consumer, and fulfiller indexed for filtering, the
|
|
7
|
+
* whole request as data so anyone can recompute the id and the fulfiller can echo it. Spelled
|
|
8
|
+
* out here because the protocol package does not depend on the contracts package; the
|
|
9
|
+
* contracts package's `test/Vrf.test.ts` decodes a real log through it and catches drift.
|
|
10
|
+
*/
|
|
11
|
+
export declare const randomnessRequestedEvent: {
|
|
12
|
+
readonly name: "RandomnessRequested";
|
|
13
|
+
readonly type: 'event';
|
|
14
|
+
readonly inputs: readonly [{
|
|
15
|
+
readonly type: "bytes32";
|
|
16
|
+
readonly name: "requestId";
|
|
17
|
+
readonly indexed: true;
|
|
18
|
+
}, {
|
|
19
|
+
readonly type: "address";
|
|
20
|
+
readonly name: "consumer";
|
|
21
|
+
readonly indexed: true;
|
|
22
|
+
}, {
|
|
23
|
+
readonly type: "address";
|
|
24
|
+
readonly name: "fulfiller";
|
|
25
|
+
readonly indexed: true;
|
|
26
|
+
}, {
|
|
27
|
+
readonly type: 'tuple';
|
|
28
|
+
readonly components: readonly [{
|
|
29
|
+
readonly type: "address";
|
|
30
|
+
readonly name: "consumer";
|
|
31
|
+
}, {
|
|
32
|
+
readonly type: "uint64";
|
|
33
|
+
readonly name: "sequence";
|
|
34
|
+
}, {
|
|
35
|
+
readonly type: "address";
|
|
36
|
+
readonly name: "fulfiller";
|
|
37
|
+
}, {
|
|
38
|
+
readonly type: "uint40";
|
|
39
|
+
readonly name: "assignedAt";
|
|
40
|
+
}, {
|
|
41
|
+
readonly type: "uint96";
|
|
42
|
+
readonly name: "fee";
|
|
43
|
+
}, {
|
|
44
|
+
readonly type: "uint96";
|
|
45
|
+
readonly name: "gasPrice";
|
|
46
|
+
}, {
|
|
47
|
+
readonly type: "uint32";
|
|
48
|
+
readonly name: "callbackGasLimit";
|
|
49
|
+
}, {
|
|
50
|
+
readonly type: "bytes";
|
|
51
|
+
readonly name: "callbackData";
|
|
52
|
+
}];
|
|
53
|
+
readonly name: "request";
|
|
54
|
+
}];
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* `VrfFacet.RandomnessFulfilled`: the id indexed, the randomness and the evidence (the proof and
|
|
58
|
+
* the enclave signature, which a challenge takes) as data. Spelled out here for the same reason
|
|
59
|
+
* as the request event, and held to the real log by the same test.
|
|
60
|
+
*/
|
|
61
|
+
export declare const randomnessFulfilledEvent: {
|
|
62
|
+
readonly name: "RandomnessFulfilled";
|
|
63
|
+
readonly type: 'event';
|
|
64
|
+
readonly inputs: readonly [{
|
|
65
|
+
readonly type: "bytes32";
|
|
66
|
+
readonly name: "requestId";
|
|
67
|
+
readonly indexed: true;
|
|
68
|
+
}, {
|
|
69
|
+
readonly type: "bytes32";
|
|
70
|
+
readonly name: "randomness";
|
|
71
|
+
}, {
|
|
72
|
+
readonly type: "uint256[4]";
|
|
73
|
+
readonly name: "proof";
|
|
74
|
+
}, {
|
|
75
|
+
readonly type: "bytes";
|
|
76
|
+
readonly name: "enclaveSignature";
|
|
77
|
+
}];
|
|
78
|
+
};
|
|
79
|
+
/** The part of a log the decoder reads, as every viem client returns it. */
|
|
80
|
+
export type EventLog = Pick<Log, "data" | "topics">;
|
|
81
|
+
/**
|
|
82
|
+
* Decodes a `RandomnessRequested` log into its id and a `Request` in the schema's shape, so
|
|
83
|
+
* the decoded request passes to `requestId` or a typed router call unchanged. Throws on a log
|
|
84
|
+
* of another event.
|
|
85
|
+
*/
|
|
86
|
+
export declare const decodeRandomnessRequested: (log: EventLog) => {
|
|
87
|
+
readonly requestId: Hex;
|
|
88
|
+
readonly request: Request;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Decodes a `RandomnessFulfilled` log into the id it closed, the randomness delivered, and the
|
|
92
|
+
* evidence (the proof and the enclave signature) a challenge or a verifier takes. Throws on a
|
|
93
|
+
* log of another event.
|
|
94
|
+
*/
|
|
95
|
+
export declare const decodeRandomnessFulfilled: (log: EventLog) => {
|
|
96
|
+
readonly requestId: Hex;
|
|
97
|
+
readonly randomness: Hex;
|
|
98
|
+
readonly proof: Proof;
|
|
99
|
+
readonly enclaveSignature: Hex;
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
package/dist/event.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { decodeEventLog, parseAbiItem } from "viem";
|
|
2
|
+
//#region src/event.ts
|
|
3
|
+
/**
|
|
4
|
+
* `VrfFacet.RandomnessRequested`: the id, consumer, and fulfiller indexed for filtering, the
|
|
5
|
+
* whole request as data so anyone can recompute the id and the fulfiller can echo it. Spelled
|
|
6
|
+
* out here because the protocol package does not depend on the contracts package; the
|
|
7
|
+
* contracts package's `test/Vrf.test.ts` decodes a real log through it and catches drift.
|
|
8
|
+
*/
|
|
9
|
+
const randomnessRequestedEvent = parseAbiItem("event RandomnessRequested(bytes32 indexed requestId, address indexed consumer, address indexed fulfiller, (address consumer, uint64 sequence, address fulfiller, uint40 assignedAt, uint96 fee, uint96 gasPrice, uint32 callbackGasLimit, bytes callbackData) request)");
|
|
10
|
+
/**
|
|
11
|
+
* `VrfFacet.RandomnessFulfilled`: the id indexed, the randomness and the evidence (the proof and
|
|
12
|
+
* the enclave signature, which a challenge takes) as data. Spelled out here for the same reason
|
|
13
|
+
* as the request event, and held to the real log by the same test.
|
|
14
|
+
*/
|
|
15
|
+
const randomnessFulfilledEvent = parseAbiItem("event RandomnessFulfilled(bytes32 indexed requestId, bytes32 randomness, uint256[4] proof, bytes enclaveSignature)");
|
|
16
|
+
/**
|
|
17
|
+
* Decodes a `RandomnessRequested` log into its id and a `Request` in the schema's shape, so
|
|
18
|
+
* the decoded request passes to `requestId` or a typed router call unchanged. Throws on a log
|
|
19
|
+
* of another event.
|
|
20
|
+
*/
|
|
21
|
+
const decodeRandomnessRequested = (log) => {
|
|
22
|
+
const { args } = decodeEventLog({
|
|
23
|
+
abi: [randomnessRequestedEvent],
|
|
24
|
+
eventName: "RandomnessRequested",
|
|
25
|
+
data: log.data,
|
|
26
|
+
topics: log.topics
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
requestId: args.requestId,
|
|
30
|
+
request: args.request
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Decodes a `RandomnessFulfilled` log into the id it closed, the randomness delivered, and the
|
|
35
|
+
* evidence (the proof and the enclave signature) a challenge or a verifier takes. Throws on a
|
|
36
|
+
* log of another event.
|
|
37
|
+
*/
|
|
38
|
+
const decodeRandomnessFulfilled = (log) => {
|
|
39
|
+
const { args } = decodeEventLog({
|
|
40
|
+
abi: [randomnessFulfilledEvent],
|
|
41
|
+
eventName: "RandomnessFulfilled",
|
|
42
|
+
data: log.data,
|
|
43
|
+
topics: log.topics
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
requestId: args.requestId,
|
|
47
|
+
randomness: args.randomness,
|
|
48
|
+
proof: args.proof,
|
|
49
|
+
enclaveSignature: args.enclaveSignature
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { decodeRandomnessFulfilled, decodeRandomnessRequested, randomnessFulfilledEvent, randomnessRequestedEvent };
|
package/dist/fee.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Hex } from "viem";
|
|
2
|
+
//#region src/fee.d.ts
|
|
3
|
+
/** EIP-1559 fees for one broadcast. */
|
|
4
|
+
export type GasFees = {
|
|
5
|
+
readonly maxFeePerGas: bigint;
|
|
6
|
+
readonly maxPriorityFeePerGas: bigint;
|
|
7
|
+
};
|
|
8
|
+
/** The fees a node's sender never goes below unless configured otherwise; tuned for Base. */
|
|
9
|
+
export declare const DEFAULT_FEE_FLOOR: GasFees;
|
|
10
|
+
/**
|
|
11
|
+
* The fees a node's sender bids given the latest base fee: twice the base fee plus the tip, never
|
|
12
|
+
* under the floor. Here because a signer mirrors it: the enclave refuses an authorization whose
|
|
13
|
+
* maximum gas price is under this bid (ADR 0028), so a client sizes its maximum from it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const computeFees: (baseFeePerGas: bigint, floor?: GasFees) => GasFees;
|
|
16
|
+
/**
|
|
17
|
+
* `Config.CALLDATA_GAS_PER_BYTE`: EIP-7623's calldata floor for a non-zero byte, charged for every
|
|
18
|
+
* echoed byte (ADR 0023).
|
|
19
|
+
*/
|
|
20
|
+
export declare const CALLDATA_GAS_PER_BYTE = 40n;
|
|
21
|
+
/**
|
|
22
|
+
* The gas price a request locks, mirroring the router's: the request transaction's gas price, never
|
|
23
|
+
* below the base fee of the block it lands in (ADR 0019). An OP-stack L1→L2 deposit transaction
|
|
24
|
+
* runs at a zero gas price; priced at that, its request would reserve nothing and its fulfillment
|
|
25
|
+
* charge nothing.
|
|
26
|
+
*/
|
|
27
|
+
export declare const lockedGasPrice: ({ gasPrice, baseFeePerGas }: {
|
|
28
|
+
readonly gasPrice: bigint;
|
|
29
|
+
readonly baseFeePerGas: bigint;
|
|
30
|
+
}) => bigint;
|
|
31
|
+
/**
|
|
32
|
+
* The fee a request locks at request time (ADR 0013, 0019, 0023):
|
|
33
|
+
* `(callbackGasLimit + fulfillmentOverheadGas + 40 × callbackDataLength) × lockedGasPrice × (100% + premium)`,
|
|
34
|
+
* rounded down to the wei, where the locked gas price is `gasPrice` floored at `baseFeePerGas`
|
|
35
|
+
* (`lockedGasPrice`). Mirrors the router's fee, so the router must multiply before it divides by
|
|
36
|
+
* 100 too. Gas amounts and the premium are accepted as `number` so a decoded request can be priced
|
|
37
|
+
* without conversion.
|
|
38
|
+
*/
|
|
39
|
+
export declare const fee: ({ callbackGasLimit, fulfillmentOverheadGas, callbackDataLength, gasPrice, baseFeePerGas, premiumPercent }: {
|
|
40
|
+
readonly callbackGasLimit: bigint | number;
|
|
41
|
+
readonly fulfillmentOverheadGas: bigint | number;
|
|
42
|
+
readonly callbackDataLength: bigint | number;
|
|
43
|
+
/** The gas price the request transaction is sent at. */
|
|
44
|
+
readonly gasPrice: bigint;
|
|
45
|
+
/** The base fee of the block the request lands in: the floor of the locked gas price. */
|
|
46
|
+
readonly baseFeePerGas: bigint;
|
|
47
|
+
readonly premiumPercent: bigint | number;
|
|
48
|
+
}) => bigint;
|
|
49
|
+
/**
|
|
50
|
+
* The most a direct request can ever charge (ADR 0028): the reservation formula at the maximum
|
|
51
|
+
* gas price the authorization names, with the same overhead and premium as an on-chain request.
|
|
52
|
+
* Mirrors the router's ceiling, which debits it from the signer's balance before the callback and
|
|
53
|
+
* credits back what the settlement does not take.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ceiling: ({ callbackGasLimit, fulfillmentOverheadGas, callbackDataLength, maxGasPrice, premiumPercent }: {
|
|
56
|
+
readonly callbackGasLimit: bigint | number;
|
|
57
|
+
readonly fulfillmentOverheadGas: bigint | number;
|
|
58
|
+
readonly callbackDataLength: bigint | number;
|
|
59
|
+
/** The most the signer agreed to pay per gas: `DirectRequest.maxGasPrice`. */
|
|
60
|
+
readonly maxGasPrice: bigint;
|
|
61
|
+
readonly premiumPercent: bigint | number;
|
|
62
|
+
}) => bigint;
|
|
63
|
+
/**
|
|
64
|
+
* What a fulfillment is owed for the gas it took, mirroring the router's settlement: that gas at
|
|
65
|
+
* the price the request locked, plus the premium, and never more than the request reserved. The
|
|
66
|
+
* submitting transaction's gas price does not change it (ADR 0019). The premium decays in a
|
|
67
|
+
* straight line with the time since the assignment (ADR 0029): whole when `settledAt`, the
|
|
68
|
+
* router's `block.timestamp`, is `assignedAt`, and nothing once the request is late (ADR 0025),
|
|
69
|
+
* so the same timing decides the quote here and on the router:
|
|
70
|
+
* `gasUsed × lockedGasPrice × (100 × D + premiumPercent × remaining) / (100 × D)`, with `D` the
|
|
71
|
+
* stale delay and `remaining` the seconds left of it (`remainingDelay`). Everything is multiplied
|
|
72
|
+
* before the one division, as on the router, so both round down to the same wei. The rest of the
|
|
73
|
+
* reservation goes back to the consumer.
|
|
74
|
+
*/
|
|
75
|
+
export declare const settlement: ({ gasUsed, requestGasPrice, premiumPercent, fee: reserved, assignedAt, settledAt }: {
|
|
76
|
+
readonly gasUsed: bigint | number;
|
|
77
|
+
readonly requestGasPrice: bigint;
|
|
78
|
+
readonly premiumPercent: bigint | number;
|
|
79
|
+
readonly fee: bigint;
|
|
80
|
+
readonly assignedAt: bigint | number;
|
|
81
|
+
readonly settledAt: bigint | number;
|
|
82
|
+
}) => bigint;
|
|
83
|
+
/**
|
|
84
|
+
* The share of the premium a settlement pays (ADR 0029), exact: `remaining` seconds of the stale
|
|
85
|
+
* delay's `delay`. A fraction and not a percentage, so a charge is reproduced from it to the wei.
|
|
86
|
+
*/
|
|
87
|
+
export type PremiumShare = {
|
|
88
|
+
readonly remaining: bigint;
|
|
89
|
+
readonly delay: bigint;
|
|
90
|
+
};
|
|
91
|
+
/** The whole premium: what the assignment's block pays, and a direct request whenever it lands. */
|
|
92
|
+
export declare const WHOLE_PREMIUM: PremiumShare;
|
|
93
|
+
/**
|
|
94
|
+
* The share of the premium a fulfillment settled at `settledAt`, the router's `block.timestamp`,
|
|
95
|
+
* was paid for a request assigned at `assignedAt`. `RequestSettled` does not carry it, so a reader
|
|
96
|
+
* of a settlement works it out from the two times, by the same `remainingDelay` the charge uses.
|
|
97
|
+
*/
|
|
98
|
+
export declare const premiumShare: ({ assignedAt, settledAt }: {
|
|
99
|
+
readonly assignedAt: bigint | number;
|
|
100
|
+
readonly settledAt: bigint | number;
|
|
101
|
+
}) => PremiumShare;
|
|
102
|
+
/**
|
|
103
|
+
* `settlement` at a share of the premium already worked out, as the router's own takes the
|
|
104
|
+
* seconds left: `gasUsed × lockedGasPrice × (100 × delay + premiumPercent × remaining) / (100 × delay)`,
|
|
105
|
+
* capped by the reservation.
|
|
106
|
+
*/
|
|
107
|
+
export declare const settlementAtShare: ({ gasUsed, requestGasPrice, premiumPercent, fee: reserved, share: { remaining, delay } }: {
|
|
108
|
+
readonly gasUsed: bigint | number;
|
|
109
|
+
readonly requestGasPrice: bigint;
|
|
110
|
+
readonly premiumPercent: bigint | number;
|
|
111
|
+
readonly fee: bigint;
|
|
112
|
+
readonly share: PremiumShare;
|
|
113
|
+
}) => bigint;
|
|
114
|
+
/**
|
|
115
|
+
* The gas a charge was computed from, which `RequestSettled` does not carry: the charge divided
|
|
116
|
+
* back by the locked price and the premium's share, rounded up, since the charge was rounded down
|
|
117
|
+
* by less than one gas's worth. `settlementAtShare` on it reproduces the charge to the wei. A
|
|
118
|
+
* charge the reservation capped says only that the gas was at least this; a gas price of nothing
|
|
119
|
+
* says nothing of the gas.
|
|
120
|
+
*/
|
|
121
|
+
export declare const chargedGas: ({ charged, requestGasPrice, premiumPercent, share: { remaining, delay } }: {
|
|
122
|
+
readonly charged: bigint;
|
|
123
|
+
readonly requestGasPrice: bigint;
|
|
124
|
+
readonly premiumPercent: bigint | number;
|
|
125
|
+
readonly share: PremiumShare;
|
|
126
|
+
}) => bigint;
|
|
127
|
+
/** Transaction intrinsic gas. */
|
|
128
|
+
export declare const INTRINSIC_GAS = 21000n;
|
|
129
|
+
/**
|
|
130
|
+
* The gas one fulfillment adds to a transaction, given the calldata that carries it, every byte
|
|
131
|
+
* at EIP-7623's non-zero floor (`CALLDATA_GAS_PER_BYTE`), below which the chain refuses a
|
|
132
|
+
* transaction whose execution is small beside its calldata (ADR 0023). The router
|
|
133
|
+
* reverts `InsufficientFulfillmentGas` unless `gasleft()` at the callback covers the callback gas
|
|
134
|
+
* limit scaled by 64/63 (EIP-150 keeps a 64th back from a call) plus what it needs afterwards, so
|
|
135
|
+
* the item needs that requirement plus everything spent before the callback. A batch's limit is
|
|
136
|
+
* the intrinsic gas plus every item's share.
|
|
137
|
+
*/
|
|
138
|
+
export declare const fulfillmentItemGas: ({ callbackGasLimit, data }: {
|
|
139
|
+
readonly callbackGasLimit: number;
|
|
140
|
+
readonly data: Hex;
|
|
141
|
+
}) => bigint;
|
|
142
|
+
//#endregion
|