Skip to content

Commit

Permalink
Merge pull request #66 from firstbatchxyz/erhant/set-setmany
Browse files Browse the repository at this point in the history
`set` and `setMany` added
  • Loading branch information
anilaltuner authored Dec 21, 2023
2 parents 95c39ac + 25e68e3 commit 8838c15
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hollowdb",
"version": "1.3.3",
"version": "1.3.4",
"description": "A decentralized privacy-preserving key-value database",
"license": "MIT",
"homepage": "https://github.com/firstbatchxyz/hollowdb#readme",
Expand Down
215 changes: 215 additions & 0 deletions src/contracts/build/hollowdb-set.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@

// src/contracts/errors/index.ts
var KeyExistsError = new ContractError("Key already exists.");
var KeyNotExistsError = new ContractError("Key does not exist.");
var CantEvolveError = new ContractError("Evolving is disabled.");
var NoVerificationKeyError = new ContractError("No verification key.");
var UnknownProtocolError = new ContractError("Unknown protocol.");
var NotWhitelistedError = new ContractError("Not whitelisted.");
var InvalidProofError = new ContractError("Invalid proof.");
var ExpectedProofError = new ContractError("Expected a proof.");
var NullValueError = new ContractError("Value cant be null, use remove instead.");
var NotOwnerError = new ContractError("Not contract owner.");
var InvalidFunctionError = new ContractError("Invalid function.");
var ArrayLengthMismatchError = new ContractError("Key and value counts mismatch.");

// src/contracts/utils/index.ts
var verifyProof = async (proof, psignals, verificationKey) => {
if (!verificationKey) {
throw NoVerificationKeyError;
}
if (verificationKey.protocol !== "groth16" && verificationKey.protocol !== "plonk") {
throw UnknownProtocolError;
}
return await SmartWeave.extensions[verificationKey.protocol].verify(verificationKey, psignals, proof);
};
var hashToGroup = (value) => {
if (value) {
return BigInt(SmartWeave.extensions.ethers.utils.ripemd160(Buffer.from(JSON.stringify(value))));
} else {
return BigInt(0);
}
};

// src/contracts/modifiers/index.ts
var onlyOwner = (caller, input, state) => {
if (caller !== state.owner) {
throw NotOwnerError;
}
return input;
};
var onlyNonNullValue = (_, input) => {
if (input.value === null) {
throw NullValueError;
}
return input;
};
var onlyNonNullValues = (_, input) => {
if (input.values.some((val) => val === null)) {
throw NullValueError;
}
return input;
};
var onlyWhitelisted = (list) => {
return (caller, input, state) => {
if (!state.isWhitelistRequired[list]) {
return input;
}
if (!state.whitelists[list][caller]) {
throw NotWhitelistedError;
}
return input;
};
};
var onlyProofVerified = (proofName, prepareInputs) => {
return async (caller, input, state) => {
if (!state.isProofRequired[proofName]) {
return input;
}
if (!input.proof) {
throw ExpectedProofError;
}
const ok = await verifyProof(
input.proof,
await prepareInputs(caller, input, state),
state.verificationKeys[proofName]
);
if (!ok) {
throw InvalidProofError;
}
return input;
};
};
async function apply(caller, input, state, ...modifiers) {
for (const modifier of modifiers) {
input = await modifier(caller, input, state);
}
return input;
}

// src/contracts/hollowdb-set.contract.ts
var handle = async (state, action) => {
const { caller, input } = action;
switch (input.function) {
case "get": {
const { key } = await apply(caller, input.value, state);
return { result: await SmartWeave.kv.get(key) };
}
case "getMany": {
const { keys } = await apply(caller, input.value, state);
const values = await Promise.all(keys.map((key) => SmartWeave.kv.get(key)));
return { result: values };
}
case "set": {
const { key, value } = await apply(caller, input.value, state, onlyWhitelisted("set"), onlyNonNullValue);
await SmartWeave.kv.put(key, value);
return { state };
}
case "setMany": {
const { keys, values } = await apply(caller, input.value, state, onlyWhitelisted("set"), onlyNonNullValues);
if (keys.length !== values.length) {
throw new ContractError("Key and value counts mismatch");
}
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
return { state };
}
case "getKeys": {
const { options } = await apply(caller, input.value, state);
return { result: await SmartWeave.kv.keys(options) };
}
case "getKVMap": {
const { options } = await apply(caller, input.value, state);
return { result: await SmartWeave.kv.kvMap(options) };
}
case "put": {
const { key, value } = await apply(caller, input.value, state, onlyWhitelisted("put"), onlyNonNullValue);
if (await SmartWeave.kv.get(key) !== null) {
throw KeyExistsError;
}
await SmartWeave.kv.put(key, value);
return { state };
}
case "putMany": {
const { keys, values } = await apply(caller, input.value, state, onlyWhitelisted("put"), onlyNonNullValues);
if (keys.length !== values.length) {
throw new ContractError("Key and value counts mismatch");
}
if (await Promise.all(keys.map((key) => SmartWeave.kv.get(key))).then((values2) => values2.some((val) => val !== null))) {
throw KeyExistsError;
}
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
return { state };
}
case "update": {
const { key, value } = await apply(
caller,
input.value,
state,
onlyNonNullValue,
onlyWhitelisted("update"),
onlyProofVerified("auth", async (_, input2) => {
const oldValue = await SmartWeave.kv.get(input2.key);
return [hashToGroup(oldValue), hashToGroup(input2.value), BigInt(input2.key)];
})
);
await SmartWeave.kv.put(key, value);
return { state };
}
case "remove": {
const { key } = await apply(
caller,
input.value,
state,
onlyWhitelisted("update"),
onlyProofVerified("auth", async (_, input2) => {
const oldValue = await SmartWeave.kv.get(input2.key);
return [hashToGroup(oldValue), BigInt(0), BigInt(input2.key)];
})
);
await SmartWeave.kv.del(key);
return { state };
}
case "updateOwner": {
const { newOwner } = await apply(caller, input.value, state, onlyOwner);
state.owner = newOwner;
return { state };
}
case "updateProofRequirement": {
const { name, value } = await apply(caller, input.value, state, onlyOwner);
state.isProofRequired[name] = value;
return { state };
}
case "updateVerificationKey": {
const { name, verificationKey } = await apply(caller, input.value, state, onlyOwner);
state.verificationKeys[name] = verificationKey;
return { state };
}
case "updateWhitelistRequirement": {
const { name, value } = await apply(caller, input.value, state, onlyOwner);
state.isWhitelistRequired[name] = value;
return { state };
}
case "updateWhitelist": {
const { add, remove, name } = await apply(caller, input.value, state, onlyOwner);
add.forEach((user) => {
state.whitelists[name][user] = true;
});
remove.forEach((user) => {
delete state.whitelists[name][user];
});
return { state };
}
case "evolve": {
const srcTxId = await apply(caller, input.value, state, onlyOwner);
if (!state.canEvolve) {
throw CantEvolveError;
}
state.evolve = srcTxId;
return { state };
}
default:
input;
throw InvalidFunctionError;
}
};

166 changes: 166 additions & 0 deletions src/contracts/hollowdb-set.contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {CantEvolveError, InvalidFunctionError, KeyExistsError} from './errors';
import {apply, onlyNonNullValue, onlyNonNullValues, onlyOwner, onlyProofVerified, onlyWhitelisted} from './modifiers';
import type {ContractHandle} from './types';
import {hashToGroup} from './utils';

type Mode = {proofs: ['auth']; whitelists: ['put', 'update', 'set']};
type Value = unknown;

export type SetInput<V> = {
function: 'set';
value: {
key: string;
value: V;
};
};

export type SetManyInput<V> = {
function: 'setMany';
value: {
keys: string[];
values: V[];
};
};

export const handle: ContractHandle<Value, Mode, SetInput<Value> | SetManyInput<Value>> = async (state, action) => {
const {caller, input} = action;
switch (input.function) {
case 'get': {
const {key} = await apply(caller, input.value, state);
return {result: (await SmartWeave.kv.get(key)) as Value | null};
}

case 'getMany': {
const {keys} = await apply(caller, input.value, state);
const values = (await Promise.all(keys.map(key => SmartWeave.kv.get(key)))) as (Value | null)[];
return {result: values};
}

case 'set': {
const {key, value} = await apply(caller, input.value, state, onlyWhitelisted('set'), onlyNonNullValue);
await SmartWeave.kv.put(key, value);
return {state};
}

case 'setMany': {
const {keys, values} = await apply(caller, input.value, state, onlyWhitelisted('set'), onlyNonNullValues);
if (keys.length !== values.length) {
throw new ContractError('Key and value counts mismatch');
}
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
return {state};
}

case 'getKeys': {
const {options} = await apply(caller, input.value, state);
return {result: await SmartWeave.kv.keys(options)};
}

case 'getKVMap': {
const {options} = await apply(caller, input.value, state);
return {result: await SmartWeave.kv.kvMap(options)};
}

case 'put': {
const {key, value} = await apply(caller, input.value, state, onlyWhitelisted('put'), onlyNonNullValue);
if ((await SmartWeave.kv.get(key)) !== null) {
throw KeyExistsError;
}
await SmartWeave.kv.put(key, value);
return {state};
}

case 'putMany': {
const {keys, values} = await apply(caller, input.value, state, onlyWhitelisted('put'), onlyNonNullValues);
if (keys.length !== values.length) {
throw new ContractError('Key and value counts mismatch');
}

if (await Promise.all(keys.map(key => SmartWeave.kv.get(key))).then(values => values.some(val => val !== null))) {
throw KeyExistsError;
}
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
return {state};
}

case 'update': {
const {key, value} = await apply(
caller,
input.value,
state,
onlyNonNullValue,
onlyWhitelisted('update'),
onlyProofVerified('auth', async (_, input) => {
const oldValue = await SmartWeave.kv.get(input.key);
return [hashToGroup(oldValue), hashToGroup(input.value), BigInt(input.key)];
})
);
await SmartWeave.kv.put(key, value);
return {state};
}

case 'remove': {
const {key} = await apply(
caller,
input.value,
state,
onlyWhitelisted('update'),
onlyProofVerified('auth', async (_, input) => {
const oldValue = await SmartWeave.kv.get(input.key);
return [hashToGroup(oldValue), BigInt(0), BigInt(input.key)];
})
);
await SmartWeave.kv.del(key);
return {state};
}

case 'updateOwner': {
const {newOwner} = await apply(caller, input.value, state, onlyOwner);
state.owner = newOwner;
return {state};
}

case 'updateProofRequirement': {
const {name, value} = await apply(caller, input.value, state, onlyOwner);
state.isProofRequired[name] = value;
return {state};
}

case 'updateVerificationKey': {
const {name, verificationKey} = await apply(caller, input.value, state, onlyOwner);
state.verificationKeys[name] = verificationKey;
return {state};
}

case 'updateWhitelistRequirement': {
const {name, value} = await apply(caller, input.value, state, onlyOwner);
state.isWhitelistRequired[name] = value;
return {state};
}

case 'updateWhitelist': {
const {add, remove, name} = await apply(caller, input.value, state, onlyOwner);
add.forEach(user => {
state.whitelists[name][user] = true;
});
remove.forEach(user => {
delete state.whitelists[name][user];
});
return {state};
}

case 'evolve': {
const srcTxId = await apply(caller, input.value, state, onlyOwner);
if (!state.canEvolve) {
throw CantEvolveError;
}
state.evolve = srcTxId;
return {state};
}

default:
// type-safe way to make sure all switch cases are handled
input satisfies never;
throw InvalidFunctionError;
}
};
Loading

0 comments on commit 8838c15

Please sign in to comment.