-
Notifications
You must be signed in to change notification settings - Fork 132
/
Copy pathhash.ts
180 lines (159 loc) · 4.18 KB
/
hash.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import { ProvableExtended } from './circuit_value.js';
import { Poseidon as Poseidon_, Field } from '../snarky.js';
import { inCheckedComputation } from './proof_system.js';
// external API
export { Poseidon, TokenSymbol };
// internal API
export {
HashInput,
prefixes,
emptyHashWithPrefix,
hashWithPrefix,
salt,
packToFields,
emptyReceiptChainHash,
};
class Sponge {
private sponge: unknown;
constructor() {
let isChecked = inCheckedComputation();
this.sponge = Poseidon_.spongeCreate(isChecked);
}
absorb(x: Field) {
Poseidon_.spongeAbsorb(this.sponge, x);
}
squeeze() {
return Poseidon_.spongeSqueeze(this.sponge);
}
}
const Poseidon = {
hash(input: Field[]) {
let isChecked = inCheckedComputation();
// this is the same:
// return Poseidon_.update(this.initialState, input, isChecked)[0];
return Poseidon_.hash(input, isChecked);
},
update(state: [Field, Field, Field], input: Field[]) {
let isChecked = inCheckedComputation();
return Poseidon_.update(state, input, isChecked);
},
get initialState(): [Field, Field, Field] {
return [Field.zero, Field.zero, Field.zero];
},
Sponge,
};
function emptyHashWithPrefix(prefix: string) {
return salt(prefix)[0];
}
function hashWithPrefix(prefix: string, input: Field[]) {
let init = salt(prefix);
return Poseidon.update(init, input)[0];
}
const prefixes: typeof Poseidon_.prefixes = new Proxy({} as any, {
// hack bc Poseidon_.prefixes is not available at start-up
get(_target, prop) {
return Poseidon_.prefixes[
prop as keyof typeof Poseidon_.prefixes
] as string;
},
});
function salt(prefix: string) {
return Poseidon_.update(
Poseidon.initialState,
[prefixToField(prefix)],
// salt is never suppoesed to run in checked mode
false
);
}
// same as Random_oracle.prefix_to_field in OCaml
function prefixToField(prefix: string) {
if (prefix.length * 8 >= 255) throw Error('prefix too long');
let bits = [...prefix]
.map((char) => {
// convert char to 8 bits
let bits = [];
for (let j = 0, c = char.charCodeAt(0); j < 8; j++, c >>= 1) {
bits.push(!!(c & 1));
}
return bits;
})
.flat();
return Field.fromBits(bits);
}
/**
* Convert the {fields, packed} hash input representation to a list of field elements
* Random_oracle_input.Chunked.pack_to_fields
*/
function packToFields({ fields = [], packed = [] }: HashInput) {
if (packed.length === 0) return fields;
let packedBits = [];
let currentPackedField = Field.zero;
let currentSize = 0;
for (let [field, size] of packed) {
currentSize += size;
if (currentSize < 255) {
currentPackedField = currentPackedField
.mul(Field(1n << BigInt(size)))
.add(field);
} else {
packedBits.push(currentPackedField);
currentSize = size;
currentPackedField = field;
}
}
packedBits.push(currentPackedField);
return fields.concat(packedBits);
}
type HashInput = { fields?: Field[]; packed?: [Field, number][] };
const HashInput = {
get empty() {
return {};
},
append(input1: HashInput, input2: HashInput) {
if (input2.fields !== undefined) {
(input1.fields ??= []).push(...input2.fields);
}
if (input2.packed !== undefined) {
(input1.packed ??= []).push(...input2.packed);
}
return input1;
},
};
type TokenSymbol = { symbol: string; field: Field };
const TokenSymbolPure: ProvableExtended<TokenSymbol, string> = {
toFields({ field }) {
return [field];
},
toAuxiliary(value) {
return [value?.symbol ?? ''];
},
fromFields([field], [symbol]) {
return { symbol, field };
},
sizeInFields() {
return 1;
},
check({ field }: TokenSymbol) {
let actual = field.rangeCheckHelper(48);
actual.assertEquals(field);
},
toJSON({ symbol }) {
return symbol;
},
toInput({ field }) {
return { packed: [[field, 48]] };
},
};
const TokenSymbol = {
...TokenSymbolPure,
get empty() {
return { symbol: '', field: Field.zero };
},
from(symbol: string): TokenSymbol {
let field = prefixToField(symbol);
return { symbol, field };
},
};
function emptyReceiptChainHash() {
return emptyHashWithPrefix('CodaReceiptEmpty');
}