@invisi-labs/primitives 0.0.0-stage → 1.2.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/README.md +4 -2
- package/dist/address.cjs +263 -0
- package/dist/address.cjs.map +1 -0
- package/dist/address.d.cts +35 -0
- package/dist/address.d.ts +35 -0
- package/dist/address.js +10 -0
- package/dist/address.js.map +1 -0
- package/dist/chunk-3SEC7POH.js +168 -0
- package/dist/chunk-3SEC7POH.js.map +1 -0
- package/dist/chunk-Q3NNEKKR.js +147 -0
- package/dist/chunk-Q3NNEKKR.js.map +1 -0
- package/dist/chunk-QAC5T4S5.js +1162 -0
- package/dist/chunk-QAC5T4S5.js.map +1 -0
- package/dist/chunk-XM4B6K37.js +21 -0
- package/dist/chunk-XM4B6K37.js.map +1 -0
- package/dist/credit.cjs +1125 -0
- package/dist/credit.cjs.map +1 -0
- package/dist/credit.d.cts +68 -0
- package/dist/credit.d.ts +68 -0
- package/dist/credit.js +42 -0
- package/dist/credit.js.map +1 -0
- package/dist/encryption-Bzsl9VQk.d.ts +35 -0
- package/dist/encryption-P2HJ89FQ.d.cts +35 -0
- package/dist/index.cjs +2796 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +511 -0
- package/dist/index.d.ts +511 -0
- package/dist/index.js +1336 -0
- package/dist/index.js.map +1 -0
- package/dist/note.cjs +887 -0
- package/dist/note.cjs.map +1 -0
- package/dist/note.d.cts +12 -0
- package/dist/note.d.ts +12 -0
- package/dist/note.js +18 -0
- package/dist/note.js.map +1 -0
- package/dist/types-CEAuh35C.d.cts +80 -0
- package/dist/types-CEAuh35C.d.ts +80 -0
- package/package.json +52 -3
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateClueKey
|
|
3
|
+
} from "./chunk-Q3NNEKKR.js";
|
|
4
|
+
|
|
5
|
+
// src/crypto/domains.ts
|
|
6
|
+
var D_IDENT = 2n;
|
|
7
|
+
var D_NOTE = 3n;
|
|
8
|
+
var D_NULL = 4n;
|
|
9
|
+
var D_CREDIT = 5n;
|
|
10
|
+
var D_CNULL = 6n;
|
|
11
|
+
var D_CREDIT_KEY_HASH = 7n;
|
|
12
|
+
var D_CREDIT_RECOVERY = 9n;
|
|
13
|
+
var D_CREDIT_STATE_NONCE = 10n;
|
|
14
|
+
var TREE_DEPTH = 16;
|
|
15
|
+
|
|
16
|
+
// src/crypto/random.ts
|
|
17
|
+
var FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
18
|
+
function sampleField(nonzero) {
|
|
19
|
+
const bytes2 = new Uint8Array(32);
|
|
20
|
+
for (; ; ) {
|
|
21
|
+
crypto.getRandomValues(bytes2);
|
|
22
|
+
const candidate = BigInt(
|
|
23
|
+
"0x" + Array.from(bytes2, (b) => b.toString(16).padStart(2, "0")).join("")
|
|
24
|
+
);
|
|
25
|
+
if (candidate < FIELD_MODULUS && (!nonzero || candidate !== 0n)) return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function generateNonce() {
|
|
29
|
+
return sampleField(false);
|
|
30
|
+
}
|
|
31
|
+
function generateCreditSharedKey() {
|
|
32
|
+
return sampleField(true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/crypto/poseidon-constants.ts
|
|
36
|
+
var INTERNAL_DIAGONAL = [
|
|
37
|
+
0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7n,
|
|
38
|
+
0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740bn,
|
|
39
|
+
0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15n,
|
|
40
|
+
0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428bn
|
|
41
|
+
];
|
|
42
|
+
var ROUND_CONSTANTS = [
|
|
43
|
+
[
|
|
44
|
+
0x19b849f69450b06848da1d39bd5e4a4302bb86744edc26238b0878e269ed23e5n,
|
|
45
|
+
0x265ddfe127dd51bd7239347b758f0a1320eb2cc7450acc1dad47f80c8dcf34d6n,
|
|
46
|
+
0x199750ec472f1809e0f66a545e1e51624108ac845015c2aa3dfc36bab497d8aan,
|
|
47
|
+
0x157ff3fe65ac7208110f06a5f74302b14d743ea25067f0ffd032f787c7f1cdf8n
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
0x2e49c43c4569dd9c5fd35ac45fca33f10b15c590692f8beefe18f4896ac94902n,
|
|
51
|
+
0x0e35fb89981890520d4aef2b6d6506c3cb2f0b6973c24fa82731345ffa2d1f1en,
|
|
52
|
+
0x251ad47cb15c4f1105f109ae5e944f1ba9d9e7806d667ffec6fe723002e0b996n,
|
|
53
|
+
0x13da07dc64d428369873e97160234641f8beb56fdd05e5f3563fa39d9c22df4en
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
0x0c009b84e650e6d23dc00c7dccef7483a553939689d350cd46e7b89055fd4738n,
|
|
57
|
+
0x011f16b1c63a854f01992e3956f42d8b04eb650c6d535eb0203dec74befdca06n,
|
|
58
|
+
0x0ed69e5e383a688f209d9a561daa79612f3f78d0467ad45485df07093f367549n,
|
|
59
|
+
0x04dba94a7b0ce9e221acad41472b6bbe3aec507f5eb3d33f463672264c9f789bn
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
0x0a3f2637d840f3a16eb094271c9d237b6036757d4bb50bf7ce732ff1d4fa28e8n,
|
|
63
|
+
0x259a666f129eea198f8a1c502fdb38fa39b1f075569564b6e54a485d1182323fn,
|
|
64
|
+
0x28bf7459c9b2f4c6d8e7d06a4ee3a47f7745d4271038e5157a32fdf7ede0d6a1n,
|
|
65
|
+
0x0a1ca941f057037526ea200f489be8d4c37c85bbcce6a2aeec91bd6941432447n
|
|
66
|
+
],
|
|
67
|
+
[0x0c6f8f958be0e93053d7fd4fc54512855535ed1539f051dcb43a26fd926361cfn, 0x0n, 0x0n, 0x0n],
|
|
68
|
+
[0x123106a93cd17578d426e8128ac9d90aa9e8a00708e296e084dd57e69caaf811n, 0x0n, 0x0n, 0x0n],
|
|
69
|
+
[0x26e1ba52ad9285d97dd3ab52f8e840085e8fa83ff1e8f1877b074867cd2dee75n, 0x0n, 0x0n, 0x0n],
|
|
70
|
+
[0x1cb55cad7bd133de18a64c5c47b9c97cbe4d8b7bf9e095864471537e6a4ae2c5n, 0x0n, 0x0n, 0x0n],
|
|
71
|
+
[0x1dcd73e46acd8f8e0e2c7ce04bde7f6d2a53043d5060a41c7143f08e6e9055d0n, 0x0n, 0x0n, 0x0n],
|
|
72
|
+
[0x011003e32f6d9c66f5852f05474a4def0cda294a0eb4e9b9b12b9bb4512e5574n, 0x0n, 0x0n, 0x0n],
|
|
73
|
+
[0x2b1e809ac1d10ab29ad5f20d03a57dfebadfe5903f58bafed7c508dd2287ae8cn, 0x0n, 0x0n, 0x0n],
|
|
74
|
+
[0x2539de1785b735999fb4dac35ee17ed0ef995d05ab2fc5faeaa69ae87bcec0a5n, 0x0n, 0x0n, 0x0n],
|
|
75
|
+
[0x0c246c5a2ef8ee0126497f222b3e0a0ef4e1c3d41c86d46e43982cb11d77951dn, 0x0n, 0x0n, 0x0n],
|
|
76
|
+
[0x192089c4974f68e95408148f7c0632edbb09e6a6ad1a1c2f3f0305f5d03b527bn, 0x0n, 0x0n, 0x0n],
|
|
77
|
+
[0x1eae0ad8ab68b2f06a0ee36eeb0d0c058529097d91096b756d8fdc2fb5a60d85n, 0x0n, 0x0n, 0x0n],
|
|
78
|
+
[0x179190e5d0e22179e46f8282872abc88db6e2fdc0dee99e69768bd98c5d06bfbn, 0x0n, 0x0n, 0x0n],
|
|
79
|
+
[0x29bb9e2c9076732576e9a81c7ac4b83214528f7db00f31bf6cafe794a9b3cd1cn, 0x0n, 0x0n, 0x0n],
|
|
80
|
+
[0x225d394e42207599403efd0c2464a90d52652645882aac35b10e590e6e691e08n, 0x0n, 0x0n, 0x0n],
|
|
81
|
+
[0x064760623c25c8cf753d238055b444532be13557451c087de09efd454b23fd59n, 0x0n, 0x0n, 0x0n],
|
|
82
|
+
[0x10ba3a0e01df92e87f301c4b716d8a394d67f4bf42a75c10922910a78f6b5b87n, 0x0n, 0x0n, 0x0n],
|
|
83
|
+
[0x0e070bf53f8451b24f9c6e96b0c2a801cb511bc0c242eb9d361b77693f21471cn, 0x0n, 0x0n, 0x0n],
|
|
84
|
+
[0x1b94cd61b051b04dd39755ff93821a73ccd6cb11d2491d8aa7f921014de252fbn, 0x0n, 0x0n, 0x0n],
|
|
85
|
+
[0x1d7cb39bafb8c744e148787a2e70230f9d4e917d5713bb050487b5aa7d74070bn, 0x0n, 0x0n, 0x0n],
|
|
86
|
+
[0x2ec93189bd1ab4f69117d0fe980c80ff8785c2961829f701bb74ac1f303b17dbn, 0x0n, 0x0n, 0x0n],
|
|
87
|
+
[0x2db366bfdd36d277a692bb825b86275beac404a19ae07a9082ea46bd83517926n, 0x0n, 0x0n, 0x0n],
|
|
88
|
+
[0x062100eb485db06269655cf186a68532985275428450359adc99cec6960711b8n, 0x0n, 0x0n, 0x0n],
|
|
89
|
+
[0x0761d33c66614aaa570e7f1e8244ca1120243f92fa59e4f900c567bf41f5a59bn, 0x0n, 0x0n, 0x0n],
|
|
90
|
+
[0x20fc411a114d13992c2705aa034e3f315d78608a0f7de4ccf7a72e494855ad0dn, 0x0n, 0x0n, 0x0n],
|
|
91
|
+
[0x25b5c004a4bdfcb5add9ec4e9ab219ba102c67e8b3effb5fc3a30f317250bc5an, 0x0n, 0x0n, 0x0n],
|
|
92
|
+
[0x23b1822d278ed632a494e58f6df6f5ed038b186d8474155ad87e7dff62b37f4bn, 0x0n, 0x0n, 0x0n],
|
|
93
|
+
[0x22734b4c5c3f9493606c4ba9012499bf0f14d13bfcfcccaa16102a29cc2f69e0n, 0x0n, 0x0n, 0x0n],
|
|
94
|
+
[0x26c0c8fe09eb30b7e27a74dc33492347e5bdff409aa3610254413d3fad795ce5n, 0x0n, 0x0n, 0x0n],
|
|
95
|
+
[0x070dd0ccb6bd7bbae88eac03fa1fbb26196be3083a809829bbd626df348ccad9n, 0x0n, 0x0n, 0x0n],
|
|
96
|
+
[0x12b6595bdb329b6fb043ba78bb28c3bec2c0a6de46d8c5ad6067c4ebfd4250dan, 0x0n, 0x0n, 0x0n],
|
|
97
|
+
[0x248d97d7f76283d63bec30e7a5876c11c06fca9b275c671c5e33d95bb7e8d729n, 0x0n, 0x0n, 0x0n],
|
|
98
|
+
[0x1a306d439d463b0816fc6fd64cc939318b45eb759ddde4aa106d15d9bd9baaaan, 0x0n, 0x0n, 0x0n],
|
|
99
|
+
[0x28a8f8372e3c38daced7c00421cb4621f4f1b54ddc27821b0d62d3d6ec7c56cfn, 0x0n, 0x0n, 0x0n],
|
|
100
|
+
[0x0094975717f9a8a8bb35152f24d43294071ce320c829f388bc852183e1e2ce7en, 0x0n, 0x0n, 0x0n],
|
|
101
|
+
[0x04d5ee4c3aa78f7d80fde60d716480d3593f74d4f653ae83f4103246db2e8d65n, 0x0n, 0x0n, 0x0n],
|
|
102
|
+
[0x2a6cf5e9aa03d4336349ad6fb8ed2269c7bef54b8822cc76d08495c12efde187n, 0x0n, 0x0n, 0x0n],
|
|
103
|
+
[0x2304d31eaab960ba9274da43e19ddeb7f792180808fd6e43baae48d7efcba3f3n, 0x0n, 0x0n, 0x0n],
|
|
104
|
+
[0x03fd9ac865a4b2a6d5e7009785817249bff08a7e0726fcb4e1c11d39d199f0b0n, 0x0n, 0x0n, 0x0n],
|
|
105
|
+
[0x00b7258ded52bbda2248404d55ee5044798afc3a209193073f7954d4d63b0b64n, 0x0n, 0x0n, 0x0n],
|
|
106
|
+
[0x159f81ada0771799ec38fca2d4bf65ebb13d3a74f3298db36272c5ca65e92d9an, 0x0n, 0x0n, 0x0n],
|
|
107
|
+
[0x1ef90e67437fbc8550237a75bc28e3bb9000130ea25f0c5471e144cf4264431fn, 0x0n, 0x0n, 0x0n],
|
|
108
|
+
[0x1e65f838515e5ff0196b49aa41a2d2568df739bc176b08ec95a79ed82932e30dn, 0x0n, 0x0n, 0x0n],
|
|
109
|
+
[0x2b1b045def3a166cec6ce768d079ba74b18c844e570e1f826575c1068c94c33fn, 0x0n, 0x0n, 0x0n],
|
|
110
|
+
[0x0832e5753ceb0ff6402543b1109229c165dc2d73bef715e3f1c6e07c168bb173n, 0x0n, 0x0n, 0x0n],
|
|
111
|
+
[0x02f614e9cedfb3dc6b762ae0a37d41bab1b841c2e8b6451bc5a8e3c390b6ad16n, 0x0n, 0x0n, 0x0n],
|
|
112
|
+
[0x0e2427d38bd46a60dd640b8e362cad967370ebb777bedff40f6a0be27e7ed705n, 0x0n, 0x0n, 0x0n],
|
|
113
|
+
[0x0493630b7c670b6deb7c84d414e7ce79049f0ec098c3c7c50768bbe29214a53an, 0x0n, 0x0n, 0x0n],
|
|
114
|
+
[0x22ead100e8e482674decdab17066c5a26bb1515355d5461a3dc06cc85327cea9n, 0x0n, 0x0n, 0x0n],
|
|
115
|
+
[0x25b3e56e655b42cdaae2626ed2554d48583f1ae35626d04de5084e0b6d2a6f16n, 0x0n, 0x0n, 0x0n],
|
|
116
|
+
[0x1e32752ada8836ef5837a6cde8ff13dbb599c336349e4c584b4fdc0a0cf6f9d0n, 0x0n, 0x0n, 0x0n],
|
|
117
|
+
[0x2fa2a871c15a387cc50f68f6f3c3455b23c00995f05078f672a9864074d412e5n, 0x0n, 0x0n, 0x0n],
|
|
118
|
+
[0x2f569b8a9a4424c9278e1db7311e889f54ccbf10661bab7fcd18e7c7a7d83505n, 0x0n, 0x0n, 0x0n],
|
|
119
|
+
[0x044cb455110a8fdd531ade530234c518a7df93f7332ffd2144165374b246b43dn, 0x0n, 0x0n, 0x0n],
|
|
120
|
+
[0x227808de93906d5d420246157f2e42b191fe8c90adfe118178ddc723a5319025n, 0x0n, 0x0n, 0x0n],
|
|
121
|
+
[0x02fcca2934e046bc623adead873579865d03781ae090ad4a8579d2e7a6800355n, 0x0n, 0x0n, 0x0n],
|
|
122
|
+
[0x0ef915f0ac120b876abccceb344a1d36bad3f3c5ab91a8ddcbec2e060d8befacn, 0x0n, 0x0n, 0x0n],
|
|
123
|
+
[
|
|
124
|
+
0x1797130f4b7a3e1777eb757bc6f287f6ab0fb85f6be63b09f3b16ef2b1405d38n,
|
|
125
|
+
0x0a76225dc04170ae3306c85abab59e608c7f497c20156d4d36c668555decc6e5n,
|
|
126
|
+
0x1fffb9ec1992d66ba1e77a7b93209af6f8fa76d48acb664796174b5326a31a5cn,
|
|
127
|
+
0x25721c4fc15a3f2853b57c338fa538d85f8fbba6c6b9c6090611889b797b9c5fn
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
0x0c817fd42d5f7a41215e3d07ba197216adb4c3790705da95eb63b982bfcaf75an,
|
|
131
|
+
0x13abe3f5239915d39f7e13c2c24970b6df8cf86ce00a22002bc15866e52b5a96n,
|
|
132
|
+
0x2106feea546224ea12ef7f39987a46c85c1bc3dc29bdbd7a92cd60acb4d391cen,
|
|
133
|
+
0x21ca859468a746b6aaa79474a37dab49f1ca5a28c748bc7157e1b3345bb0f959n
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
0x05ccd6255c1e6f0c5cf1f0df934194c62911d14d0321662a8f1a48999e34185bn,
|
|
137
|
+
0x0f0e34a64b70a626e464d846674c4c8816c4fb267fe44fe6ea28678cb09490a4n,
|
|
138
|
+
0x0558531a4e25470c6157794ca36d0e9647dbfcfe350d64838f5b1a8a2de0d4bfn,
|
|
139
|
+
0x09d3dca9173ed2faceea125157683d18924cadad3f655a60b72f5864961f1455n
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
0x0328cbd54e8c0913493f866ed03d218bf23f92d68aaec48617d4c722e5bd4335n,
|
|
143
|
+
0x2bf07216e2aff0a223a487b1a7094e07e79e7bcc9798c648ee3347dd5329d34bn,
|
|
144
|
+
0x1daf345a58006b736499c583cb76c316d6f78ed6a6dffc82111e11a63fe412dfn,
|
|
145
|
+
0x176563472456aaa746b694c60e1823611ef39039b2edc7ff391e6f2293d2c404n
|
|
146
|
+
]
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
// src/crypto/poseidon.ts
|
|
150
|
+
var mod = (n) => n % FIELD_MODULUS;
|
|
151
|
+
var fifth = (n) => {
|
|
152
|
+
const x = mod(n), x2 = mod(x * x);
|
|
153
|
+
return mod(x * x2 * x2);
|
|
154
|
+
};
|
|
155
|
+
function external(s) {
|
|
156
|
+
const t0 = s[0] + s[1], t1 = s[2] + s[3], t2 = 2n * s[1] + t1, t3 = 2n * s[3] + t0;
|
|
157
|
+
const t4 = 4n * t1 + t3, t5 = 4n * t0 + t2;
|
|
158
|
+
s[0] = mod(t3 + t5);
|
|
159
|
+
s[1] = mod(t5);
|
|
160
|
+
s[2] = mod(t2 + t4);
|
|
161
|
+
s[3] = mod(t4);
|
|
162
|
+
}
|
|
163
|
+
function permute(s) {
|
|
164
|
+
external(s);
|
|
165
|
+
for (let r = 0; r < 64; r++) {
|
|
166
|
+
const full = r < 4 || r >= 60, rc = ROUND_CONSTANTS[r];
|
|
167
|
+
if (full) {
|
|
168
|
+
for (let i = 0; i < 4; i++) s[i] = fifth(s[i] + rc[i]);
|
|
169
|
+
external(s);
|
|
170
|
+
} else {
|
|
171
|
+
s[0] = fifth(s[0] + rc[0]);
|
|
172
|
+
const sum = s.reduce((a, b) => a + b, 0n);
|
|
173
|
+
for (let i = 0; i < 4; i++) s[i] = mod(s[i] * INTERNAL_DIAGONAL[i] + sum);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function poseidonHash(inputs) {
|
|
178
|
+
for (let i = 0; i < inputs.length; i++)
|
|
179
|
+
if (inputs[i] < 0n || inputs[i] >= FIELD_MODULUS)
|
|
180
|
+
throw new RangeError(
|
|
181
|
+
`poseidonHash: input[${i}] out of BN254 field range [0, FIELD_MODULUS): ${inputs[i]}`
|
|
182
|
+
);
|
|
183
|
+
const state = [0n, 0n, 0n, BigInt(inputs.length) << 64n];
|
|
184
|
+
let used = 0;
|
|
185
|
+
for (const input of inputs) {
|
|
186
|
+
if (used === 3) {
|
|
187
|
+
permute(state);
|
|
188
|
+
used = 0;
|
|
189
|
+
}
|
|
190
|
+
state[used] = mod(state[used] + input);
|
|
191
|
+
used++;
|
|
192
|
+
}
|
|
193
|
+
permute(state);
|
|
194
|
+
return `0x${state[0].toString(16).padStart(64, "0")}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/notes/commitment.ts
|
|
198
|
+
async function computeNoteIdentity(ownerAddress, nonce) {
|
|
199
|
+
if (ownerAddress <= 0n || ownerAddress >= 1n << 160n)
|
|
200
|
+
throw new RangeError("invalid owner address");
|
|
201
|
+
return poseidonHash([D_IDENT, ownerAddress, nonce]);
|
|
202
|
+
}
|
|
203
|
+
async function computeNoteCommitment(chainId, protocolAddress, identity, tokenAddress, amount) {
|
|
204
|
+
if (chainId < 0n || chainId >= 1n << 64n) throw new RangeError("invalid chainId");
|
|
205
|
+
if (protocolAddress <= 0n || protocolAddress >= 1n << 160n)
|
|
206
|
+
throw new RangeError("invalid protocol address");
|
|
207
|
+
if (tokenAddress < 0n || tokenAddress >= 1n << 160n)
|
|
208
|
+
throw new RangeError("invalid token address");
|
|
209
|
+
if (amount <= 0n || amount >= 1n << 128n) throw new RangeError("invalid note amount");
|
|
210
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(identity)) throw new Error("invalid note identity encoding");
|
|
211
|
+
return poseidonHash([D_NOTE, chainId, protocolAddress, BigInt(identity), tokenAddress, amount]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/notes/noteContext.ts
|
|
215
|
+
function assertNoteContext(note, context) {
|
|
216
|
+
if (note.chainId !== context.chainId || note.protocolAddress !== context.protocolAddress) {
|
|
217
|
+
throw new Error("note context mismatch");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/credit/state.ts
|
|
222
|
+
var CREDIT_POLICY_FIELDS = ["singleLimit", "dailyLimit", "monthlyLimit"];
|
|
223
|
+
var CREDIT_USAGE_FIELDS = [
|
|
224
|
+
"lastDay",
|
|
225
|
+
"dailySpent",
|
|
226
|
+
"lastMonth",
|
|
227
|
+
"monthlySpent",
|
|
228
|
+
"totalSpent"
|
|
229
|
+
];
|
|
230
|
+
function normalizeCreditPolicy(value = {}) {
|
|
231
|
+
return Object.fromEntries(
|
|
232
|
+
CREDIT_POLICY_FIELDS.map((key) => {
|
|
233
|
+
const n = value[key] ?? 0n;
|
|
234
|
+
uint(n, 126, key);
|
|
235
|
+
return [key, n];
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
function normalizeCreditUsage(value = {}) {
|
|
240
|
+
const usage = Object.fromEntries(
|
|
241
|
+
CREDIT_USAGE_FIELDS.map((key) => {
|
|
242
|
+
const n = value[key] ?? 0n;
|
|
243
|
+
uint(n, key === "lastDay" || key === "lastMonth" ? 64 : 128, key);
|
|
244
|
+
return [key, n];
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
for (const [period, spent] of [
|
|
248
|
+
["lastDay", "dailySpent"],
|
|
249
|
+
["lastMonth", "monthlySpent"]
|
|
250
|
+
])
|
|
251
|
+
if (usage[period] === 0n && usage[spent] !== 0n) throw Error("noncanonical Credit usage");
|
|
252
|
+
return usage;
|
|
253
|
+
}
|
|
254
|
+
function balance(value) {
|
|
255
|
+
uint(value.availableCredit, 128, "availableCredit");
|
|
256
|
+
if (typeof value.unlimited !== "boolean") throw TypeError("unlimited must be boolean");
|
|
257
|
+
if (value.unlimited && value.availableCredit !== 0n)
|
|
258
|
+
throw Error("unlimited Credit must have zero available credit");
|
|
259
|
+
return { availableCredit: value.availableCredit, unlimited: value.unlimited };
|
|
260
|
+
}
|
|
261
|
+
function creditPeriods(timestamp) {
|
|
262
|
+
if (typeof timestamp !== "bigint") throw new TypeError("Credit timestamp required");
|
|
263
|
+
uint(timestamp, 64, "timestamp");
|
|
264
|
+
const days = timestamp / 86400n, z = days + 719468n, era = z / 146097n, doe = z - era * 146097n;
|
|
265
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
266
|
+
let year = yoe + era * 400n;
|
|
267
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n), mp = (5n * doy + 2n) / 153n;
|
|
268
|
+
const month = mp + (mp < 10n ? 3n : -9n);
|
|
269
|
+
if (month <= 2n) year++;
|
|
270
|
+
return { currentDay: days + 1n, currentMonth: (year - 1970n) * 12n + month };
|
|
271
|
+
}
|
|
272
|
+
function applyCreditDrawUsage(note, amount, timestamp) {
|
|
273
|
+
const policy = {
|
|
274
|
+
...normalizeCreditPolicy(note),
|
|
275
|
+
...normalizeCreditUsage(note),
|
|
276
|
+
...balance(note)
|
|
277
|
+
}, periods = creditPeriods(timestamp);
|
|
278
|
+
uint(amount, 126, "draw amount");
|
|
279
|
+
if (amount === 0n) throw new Error("draw amount must be positive");
|
|
280
|
+
if (policy.singleLimit !== 0n && amount > policy.singleLimit)
|
|
281
|
+
throw new Error("single limit exceeded");
|
|
282
|
+
for (const [limit, last, spent, current] of [
|
|
283
|
+
["dailyLimit", "lastDay", "dailySpent", "currentDay"],
|
|
284
|
+
["monthlyLimit", "lastMonth", "monthlySpent", "currentMonth"]
|
|
285
|
+
]) {
|
|
286
|
+
const period = periods[current];
|
|
287
|
+
if (period < policy[last]) throw new Error("Credit period rollback");
|
|
288
|
+
policy[spent] = (period === policy[last] ? policy[spent] : 0n) + amount;
|
|
289
|
+
uint(policy[spent], 128, spent);
|
|
290
|
+
policy[last] = period;
|
|
291
|
+
if (policy[limit] !== 0n && policy[spent] > policy[limit])
|
|
292
|
+
throw new Error(`${limit} exceeded`);
|
|
293
|
+
}
|
|
294
|
+
policy.totalSpent += amount;
|
|
295
|
+
uint(policy.totalSpent, 128, "totalSpent");
|
|
296
|
+
if (!policy.unlimited) {
|
|
297
|
+
if (amount > policy.availableCredit) throw Error("available credit exceeded");
|
|
298
|
+
policy.availableCredit -= amount;
|
|
299
|
+
}
|
|
300
|
+
return policy;
|
|
301
|
+
}
|
|
302
|
+
async function computeCreditSharedKeyHash(creditSharedKey) {
|
|
303
|
+
if (creditSharedKey <= 0n || creditSharedKey >= FIELD_MODULUS)
|
|
304
|
+
throw new RangeError("invalid Credit shared key");
|
|
305
|
+
return poseidonHash([D_CREDIT_KEY_HASH, creditSharedKey]);
|
|
306
|
+
}
|
|
307
|
+
function normalizeCreditState(state) {
|
|
308
|
+
const allowed = [
|
|
309
|
+
"chainId",
|
|
310
|
+
"protocolAddress",
|
|
311
|
+
"tokenAddress",
|
|
312
|
+
"spenderWalletIdentity",
|
|
313
|
+
"ownerWalletIdentity",
|
|
314
|
+
"revocationShard",
|
|
315
|
+
"revocationSlot",
|
|
316
|
+
"nonce",
|
|
317
|
+
"expiry",
|
|
318
|
+
"creditSharedKey",
|
|
319
|
+
...CREDIT_POLICY_FIELDS,
|
|
320
|
+
...CREDIT_USAGE_FIELDS,
|
|
321
|
+
"availableCredit",
|
|
322
|
+
"unlimited",
|
|
323
|
+
"payload"
|
|
324
|
+
];
|
|
325
|
+
if (!state || typeof state !== "object" || Object.keys(state).some((k) => !allowed.includes(k)))
|
|
326
|
+
throw Error("Unsupported Credit fields");
|
|
327
|
+
for (const [name, bits] of [
|
|
328
|
+
["chainId", 64],
|
|
329
|
+
["protocolAddress", 160],
|
|
330
|
+
["tokenAddress", 160],
|
|
331
|
+
["revocationShard", 64],
|
|
332
|
+
["revocationSlot", 16],
|
|
333
|
+
["expiry", 64]
|
|
334
|
+
])
|
|
335
|
+
uint(state[name], bits, name);
|
|
336
|
+
for (const id of [state.spenderWalletIdentity, state.ownerWalletIdentity])
|
|
337
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(id) || BigInt(id) === 0n)
|
|
338
|
+
throw Error("invalid Credit identity");
|
|
339
|
+
if (!state.protocolAddress || !state.tokenAddress || !state.revocationShard || !state.expiry)
|
|
340
|
+
throw Error("Credit deployment, token, shard and expiry must be positive");
|
|
341
|
+
field(state.nonce, "nonce");
|
|
342
|
+
field(state.creditSharedKey, "creditSharedKey");
|
|
343
|
+
if (!state.creditSharedKey) throw Error("Credit shared key must be positive");
|
|
344
|
+
if (state.payload !== void 0 && !(state.payload instanceof Uint8Array))
|
|
345
|
+
throw TypeError("payload must be Uint8Array");
|
|
346
|
+
return {
|
|
347
|
+
...state,
|
|
348
|
+
...normalizeCreditPolicy(state),
|
|
349
|
+
...normalizeCreditUsage(state),
|
|
350
|
+
...balance(state),
|
|
351
|
+
...state.payload !== void 0 ? { payload: Uint8Array.from(state.payload) } : {}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function computeCreditCommitment(state, keyHash) {
|
|
355
|
+
if (keyHash === void 0) state = normalizeCreditState(state);
|
|
356
|
+
for (const [name, bits] of [
|
|
357
|
+
["chainId", 64],
|
|
358
|
+
["protocolAddress", 160],
|
|
359
|
+
["tokenAddress", 160],
|
|
360
|
+
["revocationShard", 64],
|
|
361
|
+
["revocationSlot", 16],
|
|
362
|
+
["expiry", 64]
|
|
363
|
+
])
|
|
364
|
+
uint(state[name], bits, name);
|
|
365
|
+
for (const identity of [state.spenderWalletIdentity, state.ownerWalletIdentity])
|
|
366
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(identity) || BigInt(identity) === 0n)
|
|
367
|
+
throw new Error("invalid Credit identity");
|
|
368
|
+
if (state.revocationShard === 0n || state.expiry === 0n)
|
|
369
|
+
throw new Error("Credit shard and expiry must be positive");
|
|
370
|
+
field(state.nonce, "nonce");
|
|
371
|
+
const hash = keyHash ?? await computeCreditSharedKeyHash(state.creditSharedKey);
|
|
372
|
+
field(BigInt(hash), "Credit shared key hash");
|
|
373
|
+
return poseidonHash([
|
|
374
|
+
D_CREDIT,
|
|
375
|
+
state.chainId,
|
|
376
|
+
state.protocolAddress,
|
|
377
|
+
BigInt(state.spenderWalletIdentity),
|
|
378
|
+
BigInt(state.ownerWalletIdentity),
|
|
379
|
+
state.tokenAddress,
|
|
380
|
+
state.revocationShard,
|
|
381
|
+
state.revocationSlot,
|
|
382
|
+
state.nonce,
|
|
383
|
+
state.expiry,
|
|
384
|
+
BigInt(hash),
|
|
385
|
+
...CREDIT_POLICY_FIELDS.map((k) => normalizeCreditPolicy(state)[k]),
|
|
386
|
+
balance(state).availableCredit,
|
|
387
|
+
state.unlimited ? 1n : 0n,
|
|
388
|
+
...CREDIT_USAGE_FIELDS.map((k) => normalizeCreditUsage(state)[k])
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
async function computeCreditNullifier(chainId, creditSharedKey, creditCommitment2) {
|
|
392
|
+
uint(chainId, 64, "chainId");
|
|
393
|
+
if (creditSharedKey <= 0n || creditSharedKey >= FIELD_MODULUS)
|
|
394
|
+
throw new RangeError("invalid Credit shared key");
|
|
395
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(creditCommitment2))
|
|
396
|
+
throw new Error("invalid Credit commitment encoding");
|
|
397
|
+
return poseidonHash([D_CNULL, chainId, creditSharedKey, BigInt(creditCommitment2)]);
|
|
398
|
+
}
|
|
399
|
+
async function deriveCreditStateNonce(chainId, protocolAddress, tokenAddress, creditNullifier, creditSharedKey) {
|
|
400
|
+
return BigInt(
|
|
401
|
+
await poseidonHash([
|
|
402
|
+
D_CREDIT_STATE_NONCE,
|
|
403
|
+
chainId,
|
|
404
|
+
protocolAddress,
|
|
405
|
+
tokenAddress,
|
|
406
|
+
creditNullifier,
|
|
407
|
+
creditSharedKey
|
|
408
|
+
])
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
async function computeCreditRecoveryPad(chainId, protocolAddress, tokenAddress, creditNullifier, creditSharedKey) {
|
|
412
|
+
return BigInt(
|
|
413
|
+
await poseidonHash([
|
|
414
|
+
D_CREDIT_RECOVERY,
|
|
415
|
+
chainId,
|
|
416
|
+
protocolAddress,
|
|
417
|
+
tokenAddress,
|
|
418
|
+
creditNullifier,
|
|
419
|
+
creditSharedKey
|
|
420
|
+
])
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
function uint(value, bits, name) {
|
|
424
|
+
if (typeof value !== "bigint" || value < 0n || value >= 1n << BigInt(bits))
|
|
425
|
+
throw new RangeError(`${name} out of uint${bits} range`);
|
|
426
|
+
}
|
|
427
|
+
function field(value, name) {
|
|
428
|
+
if (typeof value !== "bigint" || value < 0n || value >= FIELD_MODULUS)
|
|
429
|
+
throw new RangeError(`${name} out of BN254 field range`);
|
|
430
|
+
}
|
|
431
|
+
async function recoverCreditState(context, previous, recovery) {
|
|
432
|
+
assertNoteContext(previous, context);
|
|
433
|
+
const nullifier = await computeCreditNullifier(
|
|
434
|
+
context.chainId,
|
|
435
|
+
previous.creditSharedKey,
|
|
436
|
+
await computeCreditCommitment(previous)
|
|
437
|
+
);
|
|
438
|
+
if (nullifier.toLowerCase() !== recovery.creditNullifier.toLowerCase())
|
|
439
|
+
throw new Error("previous Credit nullifier mismatch");
|
|
440
|
+
field(recovery.recoveryAmount, "recoveryAmount");
|
|
441
|
+
const args = [
|
|
442
|
+
context.chainId,
|
|
443
|
+
context.protocolAddress,
|
|
444
|
+
previous.tokenAddress,
|
|
445
|
+
BigInt(nullifier),
|
|
446
|
+
previous.creditSharedKey
|
|
447
|
+
];
|
|
448
|
+
const nonce = await deriveCreditStateNonce(...args), mask = await computeCreditRecoveryPad(...args);
|
|
449
|
+
const debit = (recovery.recoveryAmount - mask + FIELD_MODULUS) % FIELD_MODULUS;
|
|
450
|
+
uint(debit, 126, "recovered debit");
|
|
451
|
+
if (!debit) throw new Error("invalid recovered debit");
|
|
452
|
+
if (nonce === previous.nonce) throw new Error("state nonce must change");
|
|
453
|
+
const { payload, ...financial } = previous;
|
|
454
|
+
const state = {
|
|
455
|
+
...financial,
|
|
456
|
+
...applyCreditDrawUsage(previous, debit, recovery.timestamp),
|
|
457
|
+
nonce
|
|
458
|
+
};
|
|
459
|
+
if (await computeCreditCommitment(state) !== recovery.stateCommitment)
|
|
460
|
+
throw new Error("recovered Credit commitment mismatch");
|
|
461
|
+
return state;
|
|
462
|
+
}
|
|
463
|
+
async function deriveCreditState(previous, debit, timestamp) {
|
|
464
|
+
const nullifier = await computeCreditNullifier(
|
|
465
|
+
previous.chainId,
|
|
466
|
+
previous.creditSharedKey,
|
|
467
|
+
await computeCreditCommitment(previous)
|
|
468
|
+
);
|
|
469
|
+
const args = [
|
|
470
|
+
previous.chainId,
|
|
471
|
+
previous.protocolAddress,
|
|
472
|
+
previous.tokenAddress,
|
|
473
|
+
BigInt(nullifier),
|
|
474
|
+
previous.creditSharedKey
|
|
475
|
+
];
|
|
476
|
+
const nonce = await deriveCreditStateNonce(...args), mask = await computeCreditRecoveryPad(...args);
|
|
477
|
+
if (nonce === previous.nonce) throw Error("state nonce must change");
|
|
478
|
+
const state = { ...previous, ...applyCreditDrawUsage(previous, debit, timestamp), nonce };
|
|
479
|
+
return {
|
|
480
|
+
state,
|
|
481
|
+
stateCommitment: await computeCreditCommitment(state),
|
|
482
|
+
nullifier,
|
|
483
|
+
recoveryAmount: (debit + mask) % FIELD_MODULUS
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
async function deriveCreditUpdate(previous, changes, deadline) {
|
|
487
|
+
for (const key of Object.keys(changes))
|
|
488
|
+
if (![
|
|
489
|
+
...CREDIT_POLICY_FIELDS,
|
|
490
|
+
"expiry",
|
|
491
|
+
"unlimited",
|
|
492
|
+
"increaseAvailableCredit",
|
|
493
|
+
"decreaseAvailableCredit"
|
|
494
|
+
].includes(key))
|
|
495
|
+
throw Error("unsupported Credit update field");
|
|
496
|
+
const old = normalizeCreditState(previous), increase = changes.increaseAvailableCredit === void 0 ? 0n : changes.increaseAvailableCredit, decrease = changes.decreaseAvailableCredit === void 0 ? 0n : changes.decreaseAvailableCredit;
|
|
497
|
+
uint(increase, 128, "increaseAvailableCredit");
|
|
498
|
+
uint(decrease, 128, "decreaseAvailableCredit");
|
|
499
|
+
if (increase !== 0n && decrease !== 0n)
|
|
500
|
+
throw Error("Credit increase and decrease are mutually exclusive");
|
|
501
|
+
const unlimited = changes.unlimited === void 0 ? old.unlimited : changes.unlimited;
|
|
502
|
+
if (typeof unlimited !== "boolean") throw TypeError("unlimited must be boolean");
|
|
503
|
+
let availableCredit = 0n;
|
|
504
|
+
if (unlimited) {
|
|
505
|
+
if (increase !== 0n || decrease !== 0n)
|
|
506
|
+
throw Error("unlimited update cannot adjust available credit");
|
|
507
|
+
} else {
|
|
508
|
+
if (decrease > old.availableCredit)
|
|
509
|
+
throw Error("available credit decrease exceeds balance");
|
|
510
|
+
availableCredit = old.availableCredit + increase - decrease;
|
|
511
|
+
uint(availableCredit, 128, "availableCredit");
|
|
512
|
+
}
|
|
513
|
+
uint(deadline, 64, "deadline");
|
|
514
|
+
const next = {
|
|
515
|
+
...old,
|
|
516
|
+
...normalizeCreditPolicy({ ...old, ...changes }),
|
|
517
|
+
expiry: changes.expiry ?? old.expiry,
|
|
518
|
+
availableCredit,
|
|
519
|
+
unlimited
|
|
520
|
+
};
|
|
521
|
+
if (previous.expiry < deadline || next.expiry < deadline)
|
|
522
|
+
throw Error("expiry before request deadline");
|
|
523
|
+
const nullifier = await computeCreditNullifier(
|
|
524
|
+
previous.chainId,
|
|
525
|
+
previous.creditSharedKey,
|
|
526
|
+
await computeCreditCommitment(previous)
|
|
527
|
+
);
|
|
528
|
+
const nonce = await deriveCreditStateNonce(
|
|
529
|
+
previous.chainId,
|
|
530
|
+
previous.protocolAddress,
|
|
531
|
+
previous.tokenAddress,
|
|
532
|
+
BigInt(nullifier),
|
|
533
|
+
previous.creditSharedKey
|
|
534
|
+
);
|
|
535
|
+
if (nonce === previous.nonce) throw Error("state nonce must change");
|
|
536
|
+
const state = { ...next, nonce };
|
|
537
|
+
return { state, nullifier, stateCommitment: await computeCreditCommitment(state) };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/delivery/note-payload.ts
|
|
541
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
542
|
+
function concat(...parts) {
|
|
543
|
+
const out = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
544
|
+
let offset = 0;
|
|
545
|
+
for (const part of parts) {
|
|
546
|
+
out.set(part, offset);
|
|
547
|
+
offset += part.length;
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function uint2(value, bits, name) {
|
|
552
|
+
if (value < 0n || value >= 1n << BigInt(bits))
|
|
553
|
+
throw new RangeError(`${name} out of uint${bits} range`);
|
|
554
|
+
}
|
|
555
|
+
function encode(value, length = 32) {
|
|
556
|
+
uint2(value, length * 8, "encoded value");
|
|
557
|
+
const out = new Uint8Array(length);
|
|
558
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
559
|
+
out[i] = Number(value & 255n);
|
|
560
|
+
value >>= 8n;
|
|
561
|
+
}
|
|
562
|
+
return out;
|
|
563
|
+
}
|
|
564
|
+
function decode(value) {
|
|
565
|
+
return value.reduce((result, byte) => result << 8n | BigInt(byte), 0n);
|
|
566
|
+
}
|
|
567
|
+
function hexField(value) {
|
|
568
|
+
uint2(value, 160, "owner address");
|
|
569
|
+
return `0x${value.toString(16).padStart(40, "0")}`;
|
|
570
|
+
}
|
|
571
|
+
function receivingKeys(keys) {
|
|
572
|
+
if (!keys || !(keys.viewingPublicKey instanceof Uint8Array) || !(keys.clueKey instanceof Uint8Array))
|
|
573
|
+
throw new Error("receiving keys required");
|
|
574
|
+
const viewingPublicKey2 = Uint8Array.from(keys.viewingPublicKey), clueKey = Uint8Array.from(keys.clueKey);
|
|
575
|
+
if (viewingPublicKey2.length !== 33 || viewingPublicKey2[0] !== 2 && viewingPublicKey2[0] !== 3)
|
|
576
|
+
throw new Error("invalid compressed viewing public key");
|
|
577
|
+
secp256k1.ProjectivePoint.fromHex(viewingPublicKey2).assertValidity();
|
|
578
|
+
if (clueKey.length !== 32) throw new Error("invalid clue key length");
|
|
579
|
+
validateClueKey(clueKey);
|
|
580
|
+
return { viewingPublicKey: viewingPublicKey2, clueKey };
|
|
581
|
+
}
|
|
582
|
+
function normalizeCreditDeliveryState(state) {
|
|
583
|
+
if (!state || typeof state !== "object" || !state.note || typeof state.note !== "object")
|
|
584
|
+
throw new TypeError("complete Credit delivery state required");
|
|
585
|
+
const allowed = [
|
|
586
|
+
"chainId",
|
|
587
|
+
"protocolAddress",
|
|
588
|
+
"tokenAddress",
|
|
589
|
+
"spenderWalletIdentity",
|
|
590
|
+
"ownerWalletIdentity",
|
|
591
|
+
"revocationShard",
|
|
592
|
+
"revocationSlot",
|
|
593
|
+
"nonce",
|
|
594
|
+
"expiry",
|
|
595
|
+
"creditSharedKey",
|
|
596
|
+
"singleLimit",
|
|
597
|
+
"dailyLimit",
|
|
598
|
+
"monthlyLimit",
|
|
599
|
+
"availableCredit",
|
|
600
|
+
"unlimited",
|
|
601
|
+
"lastDay",
|
|
602
|
+
"dailySpent",
|
|
603
|
+
"lastMonth",
|
|
604
|
+
"monthlySpent",
|
|
605
|
+
"totalSpent",
|
|
606
|
+
"payload"
|
|
607
|
+
];
|
|
608
|
+
if (Object.keys(state.note).some((k) => !allowed.includes(k)))
|
|
609
|
+
throw Error("Unsupported Credit fields");
|
|
610
|
+
const note = normalizeCreditState(structuredClone(state.note));
|
|
611
|
+
return {
|
|
612
|
+
note: {
|
|
613
|
+
...note,
|
|
614
|
+
...normalizeCreditPolicy(note)
|
|
615
|
+
},
|
|
616
|
+
ownerReceivingKeys: receivingKeys(state.ownerReceivingKeys),
|
|
617
|
+
spenderReceivingKeys: receivingKeys(state.spenderReceivingKeys)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function encodePayload(value) {
|
|
621
|
+
if (value !== void 0 && !(value instanceof Uint8Array))
|
|
622
|
+
throw new TypeError("payload must be Uint8Array");
|
|
623
|
+
const payload = value ?? new Uint8Array();
|
|
624
|
+
if (payload.length > 65535) throw new RangeError("payload length exceeds uint16");
|
|
625
|
+
return concat(encode(BigInt(payload.length), 2), payload);
|
|
626
|
+
}
|
|
627
|
+
function readPayload(plain, offset) {
|
|
628
|
+
if (plain.length < offset + 2) throw new Error("missing payload length");
|
|
629
|
+
const length = Number(decode(plain.slice(offset, offset + 2)));
|
|
630
|
+
if (plain.length !== offset + 2 + length) throw new Error("invalid payload length");
|
|
631
|
+
return Uint8Array.from(plain.subarray(offset + 2));
|
|
632
|
+
}
|
|
633
|
+
var fundsEnvelopeV1 = {
|
|
634
|
+
kind: "funds",
|
|
635
|
+
version: 1,
|
|
636
|
+
minimum: 160,
|
|
637
|
+
maximum: 4096,
|
|
638
|
+
encode(note) {
|
|
639
|
+
return concat(
|
|
640
|
+
encode(BigInt(note.ownerAddress)),
|
|
641
|
+
encode(note.amount, 16),
|
|
642
|
+
encode(note.nonce),
|
|
643
|
+
encodePayload(note.payload)
|
|
644
|
+
);
|
|
645
|
+
},
|
|
646
|
+
decode(plain, context) {
|
|
647
|
+
return {
|
|
648
|
+
chainId: context.chainId,
|
|
649
|
+
protocolAddress: context.protocolAddress,
|
|
650
|
+
tokenAddress: context.tokenAddress,
|
|
651
|
+
ownerAddress: hexField(decode(plain.slice(0, 32))),
|
|
652
|
+
amount: decode(plain.slice(32, 48)),
|
|
653
|
+
nonce: decode(plain.slice(48, 80)),
|
|
654
|
+
payload: readPayload(plain, 80)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
var creditWireFields = [
|
|
659
|
+
["spenderWalletIdentity", 32],
|
|
660
|
+
["ownerWalletIdentity", 32],
|
|
661
|
+
["revocationShard", 8],
|
|
662
|
+
["revocationSlot", 2],
|
|
663
|
+
["nonce", 32],
|
|
664
|
+
["expiry", 8],
|
|
665
|
+
["creditSharedKey", 32],
|
|
666
|
+
["singleLimit", 16],
|
|
667
|
+
["dailyLimit", 16],
|
|
668
|
+
["monthlyLimit", 16],
|
|
669
|
+
["availableCredit", 16],
|
|
670
|
+
["unlimited", 1],
|
|
671
|
+
["lastDay", 8],
|
|
672
|
+
["dailySpent", 16],
|
|
673
|
+
["lastMonth", 8],
|
|
674
|
+
["monthlySpent", 16],
|
|
675
|
+
["totalSpent", 16]
|
|
676
|
+
];
|
|
677
|
+
var creditEnvelopeV2 = {
|
|
678
|
+
kind: "credit",
|
|
679
|
+
version: 2,
|
|
680
|
+
minimum: 485,
|
|
681
|
+
maximum: 4096,
|
|
682
|
+
encode(input) {
|
|
683
|
+
const state = normalizeCreditDeliveryState(input);
|
|
684
|
+
return concat(
|
|
685
|
+
...creditWireFields.map(([k, size]) => encode(BigInt(state.note[k]), size)),
|
|
686
|
+
state.ownerReceivingKeys.viewingPublicKey,
|
|
687
|
+
state.ownerReceivingKeys.clueKey,
|
|
688
|
+
state.spenderReceivingKeys.viewingPublicKey,
|
|
689
|
+
state.spenderReceivingKeys.clueKey,
|
|
690
|
+
encodePayload(state.note.payload)
|
|
691
|
+
);
|
|
692
|
+
},
|
|
693
|
+
decode(plain, context) {
|
|
694
|
+
let offset = 0;
|
|
695
|
+
const note = {
|
|
696
|
+
chainId: context.chainId,
|
|
697
|
+
protocolAddress: context.protocolAddress,
|
|
698
|
+
tokenAddress: context.tokenAddress
|
|
699
|
+
};
|
|
700
|
+
for (const [k, size] of creditWireFields) {
|
|
701
|
+
const n = decode(plain.slice(offset, offset + size));
|
|
702
|
+
if (k === "unlimited" && n !== 0n && n !== 1n)
|
|
703
|
+
throw Error("invalid Credit unlimited byte");
|
|
704
|
+
note[k] = k === "unlimited" ? n === 1n : k.endsWith("Identity") ? hexField(n) : n;
|
|
705
|
+
offset += size;
|
|
706
|
+
}
|
|
707
|
+
const keys = () => {
|
|
708
|
+
const k = {
|
|
709
|
+
viewingPublicKey: plain.slice(offset, offset + 33),
|
|
710
|
+
clueKey: plain.slice(offset + 33, offset + 65)
|
|
711
|
+
};
|
|
712
|
+
offset += 65;
|
|
713
|
+
return k;
|
|
714
|
+
};
|
|
715
|
+
const ownerReceivingKeys = keys(), spenderReceivingKeys = keys();
|
|
716
|
+
note.payload = readPayload(plain, offset);
|
|
717
|
+
return normalizeCreditDeliveryState({
|
|
718
|
+
note,
|
|
719
|
+
ownerReceivingKeys,
|
|
720
|
+
spenderReceivingKeys
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// src/delivery/delivery-codec.ts
|
|
726
|
+
import { getBytes } from "ethers";
|
|
727
|
+
var MAX_CIPHERTEXT_BYTES = 4096;
|
|
728
|
+
function deliveryHeader(kind) {
|
|
729
|
+
if (kind !== "funds" && kind !== "credit") throw new Error("unsupported delivery kind");
|
|
730
|
+
return kind === "credit" ? 130 : 1;
|
|
731
|
+
}
|
|
732
|
+
function decodeDeliveryHeader(header) {
|
|
733
|
+
if (!Number.isInteger(header) || header < 0 || header > 255)
|
|
734
|
+
throw new Error("delivery header must be one byte");
|
|
735
|
+
const kind = header >>> 7 ? "credit" : "funds";
|
|
736
|
+
const version = header & 127;
|
|
737
|
+
if (version === 0) throw new Error("invalid envelope version");
|
|
738
|
+
return { kind, version };
|
|
739
|
+
}
|
|
740
|
+
function deliveryCodec(header) {
|
|
741
|
+
const { kind, version } = decodeDeliveryHeader(header);
|
|
742
|
+
if (version !== (kind === "credit" ? 2 : 1))
|
|
743
|
+
throw new Error(`unsupported envelope version ${version}`);
|
|
744
|
+
const codec = kind === "funds" ? fundsEnvelopeV1 : creditEnvelopeV2;
|
|
745
|
+
return {
|
|
746
|
+
...codec,
|
|
747
|
+
domain: `Invisi.${kind === "funds" ? "Note" : "Credit"}Delivery.v${kind === "credit" ? 2 : 1}`
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function validateDeliveryCiphertext(value, kind) {
|
|
751
|
+
const data = Uint8Array.from(getBytes(value));
|
|
752
|
+
if (data.length === 0 || data.length > MAX_CIPHERTEXT_BYTES)
|
|
753
|
+
throw new Error("invalid envelope length");
|
|
754
|
+
const codec = deliveryCodec(data[0]);
|
|
755
|
+
if (kind !== void 0 && codec.kind !== kind) throw new Error("unexpected envelope kind");
|
|
756
|
+
if (data.length < codec.minimum || data.length > codec.maximum)
|
|
757
|
+
throw new Error("invalid envelope length");
|
|
758
|
+
return data;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/delivery/errors.ts
|
|
762
|
+
var DeliveryAuthenticationError = class extends Error {
|
|
763
|
+
constructor(cause) {
|
|
764
|
+
super("delivery authentication failed", { cause });
|
|
765
|
+
this.name = "DeliveryAuthenticationError";
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/delivery/envelope.ts
|
|
770
|
+
import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1";
|
|
771
|
+
|
|
772
|
+
// src/delivery/binary.ts
|
|
773
|
+
var ascii = (value) => Uint8Array.from(value, (character) => character.charCodeAt(0));
|
|
774
|
+
function concat2(...parts) {
|
|
775
|
+
const out = new Uint8Array(parts.reduce((size, p) => size + p.length, 0));
|
|
776
|
+
let offset = 0;
|
|
777
|
+
for (const part of parts) {
|
|
778
|
+
out.set(part, offset);
|
|
779
|
+
offset += part.length;
|
|
780
|
+
}
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
function bytes(value, length) {
|
|
784
|
+
let out;
|
|
785
|
+
if (typeof value === "string") {
|
|
786
|
+
if (!/^0x(?:[0-9a-fA-F]{2})*$/.test(value)) throw new Error("invalid hexadecimal bytes");
|
|
787
|
+
out = Uint8Array.from(value.slice(2).match(/../g) ?? [], (b) => parseInt(b, 16));
|
|
788
|
+
} else if (value instanceof Uint8Array) out = Uint8Array.from(value);
|
|
789
|
+
else throw new Error("expected hexadecimal bytes or Uint8Array");
|
|
790
|
+
if (out.length !== length) throw new Error(`expected ${length} bytes`);
|
|
791
|
+
return out;
|
|
792
|
+
}
|
|
793
|
+
function uint3(value, bits, name) {
|
|
794
|
+
if (value < 0n || value >= 1n << BigInt(bits))
|
|
795
|
+
throw new RangeError(`${name} out of uint${bits} range`);
|
|
796
|
+
}
|
|
797
|
+
function field2(value, name) {
|
|
798
|
+
if (value < 0n || value >= FIELD_MODULUS)
|
|
799
|
+
throw new RangeError(`${name} out of BN254 field range`);
|
|
800
|
+
}
|
|
801
|
+
function encode2(value, length = 32) {
|
|
802
|
+
uint3(value, length * 8, "encoded value");
|
|
803
|
+
const out = new Uint8Array(length);
|
|
804
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
805
|
+
out[i] = Number(value & 255n);
|
|
806
|
+
value >>= 8n;
|
|
807
|
+
}
|
|
808
|
+
return out;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/delivery/validation.ts
|
|
812
|
+
function validateContext(context, tokenAddress) {
|
|
813
|
+
uint3(context.chainId, 64, "chainId");
|
|
814
|
+
uint3(context.protocolAddress, 160, "protocolAddress");
|
|
815
|
+
uint3(tokenAddress, 160, "tokenAddress");
|
|
816
|
+
field2(BigInt(context.outputCommitment), "outputCommitment");
|
|
817
|
+
}
|
|
818
|
+
function validateNote(note) {
|
|
819
|
+
uint3(note.amount, 128, "amount");
|
|
820
|
+
if (note.amount === 0n) throw new Error("amount must be positive");
|
|
821
|
+
field2(note.nonce, "nonce");
|
|
822
|
+
uint3(BigInt(note.ownerAddress), 160, "ownerAddress");
|
|
823
|
+
if (BigInt(note.ownerAddress) === 0n) throw new Error("owner address must be positive");
|
|
824
|
+
uint3(note.tokenAddress, 160, "tokenAddress");
|
|
825
|
+
}
|
|
826
|
+
async function noteCommitment(context, note) {
|
|
827
|
+
validateContext(context, note.tokenAddress);
|
|
828
|
+
assertNoteContext(note, context);
|
|
829
|
+
validateNote(note);
|
|
830
|
+
const commitment = await computeNoteCommitment(
|
|
831
|
+
context.chainId,
|
|
832
|
+
BigInt(context.protocolAddress),
|
|
833
|
+
await computeNoteIdentity(BigInt(note.ownerAddress), note.nonce),
|
|
834
|
+
note.tokenAddress,
|
|
835
|
+
note.amount
|
|
836
|
+
);
|
|
837
|
+
if (BigInt(commitment) !== BigInt(context.outputCommitment))
|
|
838
|
+
throw new Error("output commitment mismatch");
|
|
839
|
+
}
|
|
840
|
+
async function creditCommitment(context, note) {
|
|
841
|
+
validateContext(context, note.tokenAddress);
|
|
842
|
+
assertNoteContext(note, context);
|
|
843
|
+
const commitment = await computeCreditCommitment(note);
|
|
844
|
+
if (BigInt(commitment) !== BigInt(context.outputCommitment))
|
|
845
|
+
throw new Error("credit commitment mismatch");
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/delivery/envelope.ts
|
|
849
|
+
function aad(domain, context, tokenAddress, header, role) {
|
|
850
|
+
validateContext(context, tokenAddress);
|
|
851
|
+
return concat2(
|
|
852
|
+
domain,
|
|
853
|
+
header,
|
|
854
|
+
new Uint8Array([role]),
|
|
855
|
+
encode2(context.chainId, 8),
|
|
856
|
+
encode2(context.protocolAddress, 20),
|
|
857
|
+
encode2(tokenAddress, 20),
|
|
858
|
+
encode2(BigInt(context.outputCommitment))
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
function privateKey(value) {
|
|
862
|
+
const key = bytes(value, 32);
|
|
863
|
+
if (!secp256k12.utils.isValidPrivateKey(key)) throw new Error("invalid viewing private key");
|
|
864
|
+
return key;
|
|
865
|
+
}
|
|
866
|
+
function publicKey(value) {
|
|
867
|
+
const key = bytes(value, 33);
|
|
868
|
+
if (key[0] !== 2 && key[0] !== 3) throw new Error("expected compressed secp256k1 public key");
|
|
869
|
+
secp256k12.ProjectivePoint.fromHex(key);
|
|
870
|
+
return key;
|
|
871
|
+
}
|
|
872
|
+
function viewingPublicKey(key) {
|
|
873
|
+
return secp256k12.getPublicKey(privateKey(key), true);
|
|
874
|
+
}
|
|
875
|
+
function random(length) {
|
|
876
|
+
return crypto.getRandomValues(new Uint8Array(length));
|
|
877
|
+
}
|
|
878
|
+
function ephemeralKey() {
|
|
879
|
+
for (; ; ) {
|
|
880
|
+
const key = random(32);
|
|
881
|
+
if (secp256k12.utils.isValidPrivateKey(key)) return key;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
var buffer = (value) => Uint8Array.from(value).buffer;
|
|
885
|
+
async function derivedKey(key, peer, header, recipient, domain, extractable = false) {
|
|
886
|
+
const secret = secp256k12.getSharedSecret(key, peer, true).slice(1);
|
|
887
|
+
const material = await crypto.subtle.importKey("raw", buffer(secret), "HKDF", false, [
|
|
888
|
+
"deriveKey"
|
|
889
|
+
]);
|
|
890
|
+
try {
|
|
891
|
+
return await crypto.subtle.deriveKey(
|
|
892
|
+
{
|
|
893
|
+
name: "HKDF",
|
|
894
|
+
hash: "SHA-256",
|
|
895
|
+
salt: buffer(header.slice(34, 50)),
|
|
896
|
+
info: buffer(concat2(domain, header.slice(1, 34), recipient))
|
|
897
|
+
},
|
|
898
|
+
material,
|
|
899
|
+
{ name: "AES-GCM", length: 256 },
|
|
900
|
+
extractable,
|
|
901
|
+
["encrypt", "decrypt"]
|
|
902
|
+
);
|
|
903
|
+
} finally {
|
|
904
|
+
secret.fill(0);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function seal(viewingKey, plaintext, context, tokenAddress, headerByte, role) {
|
|
908
|
+
const peer = publicKey(viewingKey), key = ephemeralKey();
|
|
909
|
+
const salt = random(16), iv = random(12), domain = deliveryDomain(headerByte);
|
|
910
|
+
const header = concat2(
|
|
911
|
+
new Uint8Array([headerByte]),
|
|
912
|
+
secp256k12.getPublicKey(key, true),
|
|
913
|
+
salt,
|
|
914
|
+
iv
|
|
915
|
+
);
|
|
916
|
+
const additionalData = aad(domain, context, tokenAddress, header, role);
|
|
917
|
+
try {
|
|
918
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
919
|
+
{
|
|
920
|
+
name: "AES-GCM",
|
|
921
|
+
iv: buffer(iv),
|
|
922
|
+
additionalData: buffer(additionalData),
|
|
923
|
+
tagLength: 128
|
|
924
|
+
},
|
|
925
|
+
await derivedKey(key, peer, header, peer, domain),
|
|
926
|
+
buffer(plaintext)
|
|
927
|
+
);
|
|
928
|
+
return concat2(header, new Uint8Array(encrypted));
|
|
929
|
+
} finally {
|
|
930
|
+
key.fill(0);
|
|
931
|
+
plaintext.fill(0);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
async function open(key, envelope, context, headerByte, role, payloadKey = false) {
|
|
935
|
+
const data = envelopeBytes(envelope, deliveryCodec(headerByte).kind === "credit");
|
|
936
|
+
if (data[0] !== headerByte) throw new Error("unsupported envelope version");
|
|
937
|
+
const domain = deliveryDomain(headerByte);
|
|
938
|
+
const additionalData = aad(domain, context, context.tokenAddress, data.slice(0, 62), role);
|
|
939
|
+
const localKey = payloadKey ? bytes(key, 32) : privateKey(key);
|
|
940
|
+
try {
|
|
941
|
+
const aes = payloadKey ? await crypto.subtle.importKey("raw", buffer(bytes(key, 32)), "AES-GCM", false, [
|
|
942
|
+
"decrypt"
|
|
943
|
+
]) : await derivedKey(
|
|
944
|
+
localKey,
|
|
945
|
+
publicKey(data.slice(1, 34)),
|
|
946
|
+
data.slice(0, 62),
|
|
947
|
+
secp256k12.getPublicKey(localKey, true),
|
|
948
|
+
domain
|
|
949
|
+
);
|
|
950
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
951
|
+
{
|
|
952
|
+
name: "AES-GCM",
|
|
953
|
+
iv: buffer(data.slice(50, 62)),
|
|
954
|
+
additionalData: buffer(additionalData),
|
|
955
|
+
tagLength: 128
|
|
956
|
+
},
|
|
957
|
+
aes,
|
|
958
|
+
buffer(data.slice(62))
|
|
959
|
+
).catch((error) => {
|
|
960
|
+
if (error instanceof Error && error.name === "OperationError")
|
|
961
|
+
throw new DeliveryAuthenticationError(error);
|
|
962
|
+
throw error;
|
|
963
|
+
});
|
|
964
|
+
return new Uint8Array(decrypted);
|
|
965
|
+
} finally {
|
|
966
|
+
localKey.fill(0);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
function deliveryDomain(headerByte) {
|
|
970
|
+
return ascii(deliveryCodec(headerByte).domain);
|
|
971
|
+
}
|
|
972
|
+
async function exportPayloadKey(key, envelope, credit) {
|
|
973
|
+
const data = envelopeBytes(envelope, credit);
|
|
974
|
+
const localKey = privateKey(key);
|
|
975
|
+
try {
|
|
976
|
+
const aes = await derivedKey(
|
|
977
|
+
localKey,
|
|
978
|
+
publicKey(data.slice(1, 34)),
|
|
979
|
+
data.slice(0, 62),
|
|
980
|
+
viewingPublicKey(localKey),
|
|
981
|
+
deliveryDomain(data[0]),
|
|
982
|
+
true
|
|
983
|
+
);
|
|
984
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", aes));
|
|
985
|
+
} finally {
|
|
986
|
+
localKey.fill(0);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function envelopeBytes(value, credit) {
|
|
990
|
+
return validateDeliveryCiphertext(value, credit ? "credit" : "funds");
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/delivery/encryption.ts
|
|
994
|
+
function copyPayload(value, codec) {
|
|
995
|
+
if (!(value instanceof Uint8Array)) throw new Error("codec encode must return Uint8Array");
|
|
996
|
+
if (value.length + 78 < codec.minimum || value.length + 78 > codec.maximum)
|
|
997
|
+
throw new Error("invalid codec payload length");
|
|
998
|
+
return Uint8Array.from(value);
|
|
999
|
+
}
|
|
1000
|
+
async function encryptPrivateTransferNote(viewingKey, note, context) {
|
|
1001
|
+
const scopedNote = structuredClone(note), scopedContext = { ...context };
|
|
1002
|
+
const codec = deliveryCodec(deliveryHeader("funds"));
|
|
1003
|
+
if (codec.kind !== "funds") throw new Error("unexpected envelope kind");
|
|
1004
|
+
await noteCommitment(scopedContext, scopedNote);
|
|
1005
|
+
const plaintext = copyPayload(codec.encode(structuredClone(scopedNote)), codec);
|
|
1006
|
+
return seal(
|
|
1007
|
+
viewingKey,
|
|
1008
|
+
plaintext,
|
|
1009
|
+
scopedContext,
|
|
1010
|
+
scopedNote.tokenAddress,
|
|
1011
|
+
deliveryHeader("funds"),
|
|
1012
|
+
0
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
async function readNote(key, envelope, context, payloadKey = false) {
|
|
1016
|
+
const authenticatedContext = { ...context };
|
|
1017
|
+
const data = envelopeBytes(envelope, false);
|
|
1018
|
+
const headerByte = data[0];
|
|
1019
|
+
const plain = await open(key, data, authenticatedContext, headerByte, 0, payloadKey);
|
|
1020
|
+
try {
|
|
1021
|
+
const codec = deliveryCodec(headerByte);
|
|
1022
|
+
if (codec.kind !== "funds") throw new Error("unexpected envelope kind");
|
|
1023
|
+
const note = structuredClone(
|
|
1024
|
+
codec.decode(plain, Object.freeze({ ...authenticatedContext }))
|
|
1025
|
+
);
|
|
1026
|
+
if (BigInt(note.ownerAddress) !== BigInt(authenticatedContext.ownerAddress))
|
|
1027
|
+
throw new Error("recipient owner address mismatch");
|
|
1028
|
+
if (note.tokenAddress !== authenticatedContext.tokenAddress)
|
|
1029
|
+
throw new Error("note token context mismatch");
|
|
1030
|
+
await noteCommitment(authenticatedContext, note);
|
|
1031
|
+
return note;
|
|
1032
|
+
} finally {
|
|
1033
|
+
plain.fill(0);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function creditRole(role) {
|
|
1037
|
+
if (role !== "owner" && role !== "spender")
|
|
1038
|
+
throw new Error("Credit role must be owner or spender");
|
|
1039
|
+
return role === "owner" ? 1 : 2;
|
|
1040
|
+
}
|
|
1041
|
+
function matchCreditRecipient(state, role, recipient) {
|
|
1042
|
+
const keys = role === "owner" ? state.ownerReceivingKeys : state.spenderReceivingKeys;
|
|
1043
|
+
if (!keys.viewingPublicKey.every((value, index) => value === recipient[index]))
|
|
1044
|
+
throw new Error("recipient receiving key mismatch");
|
|
1045
|
+
}
|
|
1046
|
+
async function encryptCreditState(viewingKey, state, context) {
|
|
1047
|
+
const scopedState = normalizeCreditDeliveryState(state), scopedNote = scopedState.note;
|
|
1048
|
+
const scopedContext = { ...context };
|
|
1049
|
+
const codec = deliveryCodec(deliveryHeader("credit"));
|
|
1050
|
+
if (codec.kind !== "credit") throw new Error("unexpected envelope kind");
|
|
1051
|
+
creditRole(scopedContext.role);
|
|
1052
|
+
matchCreditRecipient(scopedState, scopedContext.role, publicKey(viewingKey));
|
|
1053
|
+
await creditCommitment(scopedContext, scopedNote);
|
|
1054
|
+
const plaintext = copyPayload(codec.encode(structuredClone(scopedState)), codec);
|
|
1055
|
+
return seal(
|
|
1056
|
+
viewingKey,
|
|
1057
|
+
plaintext,
|
|
1058
|
+
scopedContext,
|
|
1059
|
+
scopedNote.tokenAddress,
|
|
1060
|
+
deliveryHeader("credit"),
|
|
1061
|
+
creditRole(scopedContext.role)
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
async function readCredit(key, envelope, context, payloadKey = false) {
|
|
1065
|
+
const authenticatedContext = { ...context };
|
|
1066
|
+
const data = envelopeBytes(envelope, true);
|
|
1067
|
+
const plain = await open(
|
|
1068
|
+
key,
|
|
1069
|
+
data,
|
|
1070
|
+
authenticatedContext,
|
|
1071
|
+
data[0],
|
|
1072
|
+
creditRole(authenticatedContext.role),
|
|
1073
|
+
payloadKey
|
|
1074
|
+
);
|
|
1075
|
+
try {
|
|
1076
|
+
const codec = deliveryCodec(data[0]);
|
|
1077
|
+
if (codec.kind !== "credit") throw new Error("unexpected envelope kind");
|
|
1078
|
+
const state = normalizeCreditDeliveryState(
|
|
1079
|
+
structuredClone(codec.decode(plain, Object.freeze({ ...authenticatedContext })))
|
|
1080
|
+
);
|
|
1081
|
+
const note = state.note;
|
|
1082
|
+
if (BigInt(
|
|
1083
|
+
authenticatedContext.role === "owner" ? note.ownerWalletIdentity : note.spenderWalletIdentity
|
|
1084
|
+
) !== BigInt(authenticatedContext.ownerAddress))
|
|
1085
|
+
throw new Error("recipient owner address mismatch");
|
|
1086
|
+
if (!payloadKey)
|
|
1087
|
+
matchCreditRecipient(state, authenticatedContext.role, viewingPublicKey(key));
|
|
1088
|
+
if (note.tokenAddress !== authenticatedContext.tokenAddress)
|
|
1089
|
+
throw new Error("Credit token context mismatch");
|
|
1090
|
+
await creditCommitment(authenticatedContext, note);
|
|
1091
|
+
return {
|
|
1092
|
+
...state,
|
|
1093
|
+
note: { ...state.note, payload: Uint8Array.from(state.note.payload ?? []) }
|
|
1094
|
+
};
|
|
1095
|
+
} finally {
|
|
1096
|
+
plain.fill(0);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function decryptPrivateTransferNote(key, envelope, context) {
|
|
1100
|
+
return readNote(key, envelope, context);
|
|
1101
|
+
}
|
|
1102
|
+
function decryptCreditState(key, envelope, context) {
|
|
1103
|
+
return readCredit(key, envelope, context);
|
|
1104
|
+
}
|
|
1105
|
+
function decryptNotePayload(key, envelope, context) {
|
|
1106
|
+
return readNote(key, envelope, context, true);
|
|
1107
|
+
}
|
|
1108
|
+
function decryptCreditPayload(key, envelope, context) {
|
|
1109
|
+
return readCredit(key, envelope, context, true);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
export {
|
|
1113
|
+
D_IDENT,
|
|
1114
|
+
D_NOTE,
|
|
1115
|
+
D_NULL,
|
|
1116
|
+
D_CREDIT,
|
|
1117
|
+
D_CNULL,
|
|
1118
|
+
D_CREDIT_KEY_HASH,
|
|
1119
|
+
D_CREDIT_RECOVERY,
|
|
1120
|
+
D_CREDIT_STATE_NONCE,
|
|
1121
|
+
TREE_DEPTH,
|
|
1122
|
+
FIELD_MODULUS,
|
|
1123
|
+
generateNonce,
|
|
1124
|
+
generateCreditSharedKey,
|
|
1125
|
+
poseidonHash,
|
|
1126
|
+
computeNoteIdentity,
|
|
1127
|
+
computeNoteCommitment,
|
|
1128
|
+
CREDIT_POLICY_FIELDS,
|
|
1129
|
+
CREDIT_USAGE_FIELDS,
|
|
1130
|
+
normalizeCreditPolicy,
|
|
1131
|
+
normalizeCreditUsage,
|
|
1132
|
+
creditPeriods,
|
|
1133
|
+
applyCreditDrawUsage,
|
|
1134
|
+
computeCreditSharedKeyHash,
|
|
1135
|
+
normalizeCreditState,
|
|
1136
|
+
computeCreditCommitment,
|
|
1137
|
+
computeCreditNullifier,
|
|
1138
|
+
deriveCreditStateNonce,
|
|
1139
|
+
computeCreditRecoveryPad,
|
|
1140
|
+
recoverCreditState,
|
|
1141
|
+
deriveCreditState,
|
|
1142
|
+
deriveCreditUpdate,
|
|
1143
|
+
normalizeCreditDeliveryState,
|
|
1144
|
+
MAX_CIPHERTEXT_BYTES,
|
|
1145
|
+
deliveryHeader,
|
|
1146
|
+
decodeDeliveryHeader,
|
|
1147
|
+
deliveryCodec,
|
|
1148
|
+
validateDeliveryCiphertext,
|
|
1149
|
+
DeliveryAuthenticationError,
|
|
1150
|
+
validateContext,
|
|
1151
|
+
noteCommitment,
|
|
1152
|
+
creditCommitment,
|
|
1153
|
+
viewingPublicKey,
|
|
1154
|
+
exportPayloadKey,
|
|
1155
|
+
encryptPrivateTransferNote,
|
|
1156
|
+
encryptCreditState,
|
|
1157
|
+
decryptPrivateTransferNote,
|
|
1158
|
+
decryptCreditState,
|
|
1159
|
+
decryptNotePayload,
|
|
1160
|
+
decryptCreditPayload
|
|
1161
|
+
};
|
|
1162
|
+
//# sourceMappingURL=chunk-QAC5T4S5.js.map
|