mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
move dao/schema.rs to example (dao)
This commit is contained in:
@@ -360,8 +360,8 @@ required-features = ["crypto"]
|
||||
|
||||
[[example]]
|
||||
name = "dao"
|
||||
path = "example/dao/dao.rs"
|
||||
required-features = ["crypto"]
|
||||
path = "example/dao/src/dao.rs"
|
||||
required-features = ["crypto", "rpc"]
|
||||
|
||||
[[example]]
|
||||
name = "lead"
|
||||
|
||||
41
example/dao/Cargo.toml
Normal file
41
example/dao/Cargo.toml
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "dao"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
darkfi = {path = "../../", features = ["rpc", "crypto", "tx", "node"]}
|
||||
darkfi-serial = {path = "../../src/serial"}
|
||||
|
||||
# Async
|
||||
smol = "1.2.5"
|
||||
futures = "0.3.24"
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
async-trait = "0.1.57"
|
||||
async-channel = "1.7.1"
|
||||
async-executor = "1.4.1"
|
||||
easy-parallel = "3.2.0"
|
||||
|
||||
# Misc
|
||||
log = "0.4.17"
|
||||
num_cpus = "1.13.1"
|
||||
simplelog = "0.12.0"
|
||||
thiserror = "1.0.37"
|
||||
|
||||
# Crypto
|
||||
incrementalmerkletree = "0.3.0"
|
||||
pasta_curves = "0.4.0"
|
||||
halo2_gadgets = "0.2.0"
|
||||
halo2_proofs = "0.2.0"
|
||||
rand = "0.8.5"
|
||||
crypto_api_chachapoly = "0.5.0"
|
||||
group = "0.12.0"
|
||||
|
||||
# Encoding and parsing
|
||||
serde_json = "1.0.85"
|
||||
bs58 = "0.4.0"
|
||||
fxhash = "0.2.1"
|
||||
|
||||
# Utilities
|
||||
lazy_static = "1.4.0"
|
||||
url = "2.3.1"
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod dao_contract;
|
||||
pub mod example_contract;
|
||||
pub mod money_contract;
|
||||
@@ -91,7 +91,7 @@ circuit "DaoExec" {
|
||||
proposal_token_id,
|
||||
dao_serial,
|
||||
dao_spend_hook,
|
||||
proposal_bulla,
|
||||
dao_bulla,
|
||||
dao_coin_blind,
|
||||
);
|
||||
constrain_instance(coin_1);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
class ClassNamespace(SimpleNamespace):
|
||||
def __init__(self, dic=None):
|
||||
if dic is None:
|
||||
return
|
||||
# if type(dic) is dict:
|
||||
for key in dic:
|
||||
setattr(self, key, self.envelop(dic[key]))
|
||||
# else:
|
||||
# raise CatalogError("ClassNamespace AIUTO!")
|
||||
|
||||
def envelop(self, elem):
|
||||
if type(elem) is dict:
|
||||
return ClassNamespace(elem)
|
||||
elif type(elem) is list:
|
||||
return [self.envelop(x) for x in elem]
|
||||
else:
|
||||
return elem
|
||||
|
||||
# if d is not None:
|
||||
# for key in d:
|
||||
# if type(d[key]) is dict:
|
||||
# setattr(self, key, ClassNamespace(d[key]))
|
||||
# else:
|
||||
# setattr(self, key, d[key])
|
||||
|
||||
def __contains__(self, x):
|
||||
return x in self.__dict__
|
||||
|
||||
def __json__(self, x):
|
||||
return self.__dict__
|
||||
|
||||
def copy(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
def classcopy(self):
|
||||
dummy = ClassNamespace()
|
||||
dummy.__dict__.update(self.__dict__)
|
||||
return dummy
|
||||
|
||||
def dictcopy(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
def update(self, oth):
|
||||
self.__dict__.update(oth.__dict__)
|
||||
@@ -1,158 +0,0 @@
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
def ff_inv(a, p):
|
||||
a %= p
|
||||
|
||||
# extended euclidean algorithm
|
||||
# ps + at = 1
|
||||
t = 0
|
||||
new_t = 1
|
||||
r = p
|
||||
new_r = a
|
||||
|
||||
while new_r != 0:
|
||||
quotient = r // new_r
|
||||
t, new_t = new_t, t - quotient * new_t
|
||||
r, new_r = new_r, r - quotient * new_r
|
||||
|
||||
assert r == 1
|
||||
if t < 0:
|
||||
t += p
|
||||
|
||||
return t
|
||||
|
||||
class EllipticCurve:
|
||||
|
||||
def __init__(self, p, A, B, order, G, H, J):
|
||||
self.p = p
|
||||
self.A = A
|
||||
self.B = B
|
||||
self.order = order
|
||||
self.G = G
|
||||
self.H = H
|
||||
self.J = J
|
||||
assert self.is_valid(G)
|
||||
assert self.is_valid(H)
|
||||
|
||||
def is_valid(self, P):
|
||||
x, y, z = P
|
||||
if z == 0:
|
||||
return x != 0 or y != 0
|
||||
z_inv = ff_inv(z, self.p)
|
||||
x, y = x * z_inv, y * z_inv
|
||||
return y**2 % self.p == (x**3 + self.A * x + self.B) % self.p
|
||||
|
||||
def add(self, p1, p2):
|
||||
x1, y1, z1 = p1
|
||||
x2, y2, z2 = p2
|
||||
|
||||
if z1 == 0:
|
||||
return (x2, y2, z2)
|
||||
elif z2 == 0:
|
||||
return (x1, y1, z1)
|
||||
|
||||
if x1 == x2:
|
||||
if y1 != y2:
|
||||
return (0, 1, 0)
|
||||
|
||||
assert y1 != 0
|
||||
m = (3 * x1**2 + self.A) * ff_inv(2*y1, self.p)
|
||||
else:
|
||||
m = (y2 - y1) * ff_inv(x2 - x1, self.p)
|
||||
|
||||
x3 = (m**2 - x1 - x2) % self.p
|
||||
y3 = (m * (x1 - x3) - y1) % self.p
|
||||
return (x3, y3, 1)
|
||||
|
||||
def multiply(self, m, p):
|
||||
bits = f"{m:b}"
|
||||
result = (0, 1, 0)
|
||||
temp = p
|
||||
for bit in bits[::-1]:
|
||||
if bit == "1":
|
||||
result = self.add(result, temp)
|
||||
temp = self.add(temp, temp)
|
||||
return result
|
||||
|
||||
def random_point(self):
|
||||
m = self.random_scalar()
|
||||
return self.multiply(m, self.G)
|
||||
|
||||
def random_scalar(self):
|
||||
m = random.randrange(0, self.order - 1)
|
||||
return m
|
||||
|
||||
def random_base(self):
|
||||
m = random.randrange(0, self.p - 1)
|
||||
return m
|
||||
|
||||
def pallas_curve():
|
||||
# Pallas
|
||||
p = 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001
|
||||
q = 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001
|
||||
G = (5, 5392431450607408583390510508521091931943415030464003135511088002453056875732, 1)
|
||||
H = (9762257241998025279988087154025308614062019274413483967640476725944341089207,
|
||||
12058632856930756995627167820351407063813260358041446014729496773111030695755, 1)
|
||||
J = (7795559447963065356059848000022900528974048197507738248625163674930282081839,
|
||||
5156492880772775379342191094371887365795329446468828588866320184016504353483, 1)
|
||||
ec = EllipticCurve(p, 0, 5, q, G, H, J)
|
||||
A = (144931808354919915876542440378319484704499556634959420306426167479163065488,
|
||||
2699682121356767698440748624399854659825391162912545787181017961871465868196, 1)
|
||||
B = (16017037670495191561606513965775243786961447026019262496667491008912834496943,
|
||||
20395164507282344548629891414360366999207473153143014512687861307997120664849, 1)
|
||||
assert ec.add(A, B) == (2414658659502531855741199170408914396997834981355655923471364687102714431309, 21133344194418979683767005688724798091220515434220043854575260979109407444719, 1)
|
||||
m = 26322809409216846271933211244226061368157231119725763192402071651286829040466
|
||||
assert ec.multiply(m, G) == (15862887453366837597569434439063150886012590021428640083047997467990450633825, 25887284719793568129480941070850220101898092026705204234126448799557008384178, 1)
|
||||
return ec
|
||||
|
||||
def pedersen_encrypt(x, y, ec):
|
||||
vcv = ec.multiply(x, ec.G)
|
||||
vcr = ec.multiply(y, ec.H)
|
||||
return ec.add(vcv, vcr)
|
||||
|
||||
def _add_to_hasher(hasher, args):
|
||||
for arg in args:
|
||||
match arg:
|
||||
case int() as arg:
|
||||
hasher.update(arg.to_bytes(32, byteorder="little"))
|
||||
case bytes() as arg:
|
||||
hasher.update(arg)
|
||||
case list() as arg:
|
||||
_add_to_hasher(hasher, arg)
|
||||
case _:
|
||||
raise Exception(f"unknown hash arg '{arg}' type: {type(arg)}")
|
||||
|
||||
def ff_hash(p, *args):
|
||||
hasher = hashlib.sha256()
|
||||
_add_to_hasher(hasher, args)
|
||||
value = int.from_bytes(hasher.digest(), byteorder="little")
|
||||
return value % p
|
||||
|
||||
def hash_point(point, message=None):
|
||||
hasher = hashlib.sha256()
|
||||
for x_i in point:
|
||||
hasher.update(x_i.to_bytes(32, byteorder="little"))
|
||||
# Optional message
|
||||
if message is not None:
|
||||
hasher.update(message)
|
||||
value = int.from_bytes(hasher.digest(), byteorder="little")
|
||||
return value
|
||||
|
||||
def sign(message, secret, ec):
|
||||
ephem_secret = ec.random_scalar()
|
||||
ephem_public = ec.multiply(ephem_secret, ec.G)
|
||||
challenge = hash_point(ephem_public, message) % ec.order
|
||||
response = (ephem_secret + challenge * secret) % ec.order
|
||||
return ephem_public, response
|
||||
|
||||
def verify(message, signature, public, ec):
|
||||
ephem_public, response = signature
|
||||
challenge = hash_point(ephem_public, message) % ec.order
|
||||
# sG
|
||||
lhs = ec.multiply(response, ec.G)
|
||||
# R + cP
|
||||
rhs_cP = ec.multiply(challenge, public)
|
||||
rhs = ec.add(ephem_public, rhs_cP)
|
||||
return lhs == rhs
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,331 +0,0 @@
|
||||
from classnamespace import ClassNamespace
|
||||
from crypto import ff_hash, pedersen_encrypt, sign, verify
|
||||
|
||||
# Tx representing send_payment() contract call
|
||||
class SendPaymentTxBuilder:
|
||||
|
||||
def __init__(self, ec):
|
||||
self.clear_inputs = []
|
||||
self.inputs = []
|
||||
self.outputs = []
|
||||
self.input_blinds = []
|
||||
self.output_blinds = []
|
||||
|
||||
self.ec = ec
|
||||
|
||||
def add_clear_input(self, value, token_id, signature_secret):
|
||||
clear_input = ClassNamespace()
|
||||
clear_input.value = value
|
||||
clear_input.token_id = token_id
|
||||
clear_input.signature_secret = signature_secret
|
||||
self.clear_inputs.append(clear_input)
|
||||
|
||||
def add_input(self, all_coins, secret, note, user_data_blind):
|
||||
input = ClassNamespace()
|
||||
input.all_coins = all_coins
|
||||
input.secret = secret
|
||||
input.note = note
|
||||
input.user_data_blind = user_data_blind
|
||||
self.inputs.append(input)
|
||||
|
||||
def add_output(self, value, token_id, public, spend_hook, user_data):
|
||||
output = ClassNamespace()
|
||||
output.value = value
|
||||
output.token_id = token_id
|
||||
output.public = public
|
||||
output.spend_hook = spend_hook
|
||||
output.user_data = user_data
|
||||
self.outputs.append(output)
|
||||
|
||||
def compute_remainder_blind(self, clear_inputs, input_blinds,
|
||||
output_blinds):
|
||||
total = 0
|
||||
total += sum(input.value_blind for input in clear_inputs)
|
||||
total += sum(input_blinds)
|
||||
total -= sum(output_blinds)
|
||||
return total % self.ec.order
|
||||
|
||||
def build(self):
|
||||
tx = SendPaymentTx(self.ec)
|
||||
token_blind = self.ec.random_scalar()
|
||||
|
||||
for input in self.clear_inputs:
|
||||
tx_clear_input = ClassNamespace()
|
||||
tx_clear_input.__name__ = "TransactionClearInput"
|
||||
tx_clear_input.value = input.value
|
||||
tx_clear_input.token_id = input.token_id
|
||||
tx_clear_input.value_blind = self.ec.random_scalar()
|
||||
tx_clear_input.token_blind = token_blind
|
||||
tx_clear_input.signature_public = self.ec.multiply(
|
||||
input.signature_secret, self.ec.G)
|
||||
tx.clear_inputs.append(tx_clear_input)
|
||||
|
||||
self.input_blinds = []
|
||||
signature_secrets = []
|
||||
for input in self.inputs:
|
||||
value_blind = self.ec.random_scalar()
|
||||
self.input_blinds.append(value_blind)
|
||||
|
||||
signature_secret = self.ec.random_scalar()
|
||||
signature_secrets.append(signature_secret)
|
||||
|
||||
tx_input = ClassNamespace()
|
||||
tx_input.__name__ = "TransactionInput"
|
||||
tx_input.burn_proof = BurnProof(
|
||||
input.note.value, input.note.token_id, value_blind,
|
||||
token_blind, input.note.serial, input.note.coin_blind,
|
||||
input.secret, input.note.spend_hook, input.note.user_data,
|
||||
input.user_data_blind, input.all_coins, signature_secret,
|
||||
self.ec)
|
||||
tx_input.revealed = tx_input.burn_proof.get_revealed()
|
||||
tx.inputs.append(tx_input)
|
||||
|
||||
assert self.outputs
|
||||
self.output_blinds = []
|
||||
for i, output in enumerate(self.outputs):
|
||||
if i == len(self.outputs) - 1:
|
||||
value_blind = self.compute_remainder_blind(
|
||||
tx.clear_inputs, self.input_blinds, self.output_blinds)
|
||||
else:
|
||||
value_blind = self.ec.random_scalar()
|
||||
self.output_blinds.append(value_blind)
|
||||
|
||||
note = ClassNamespace()
|
||||
note.serial = self.ec.random_base()
|
||||
note.value = output.value
|
||||
note.token_id = output.token_id
|
||||
note.coin_blind = self.ec.random_base()
|
||||
note.value_blind = value_blind
|
||||
note.token_blind = token_blind
|
||||
note.spend_hook = output.spend_hook
|
||||
note.user_data = output.user_data
|
||||
|
||||
tx_output = ClassNamespace()
|
||||
tx_output.__name__ = "TransactionOutput"
|
||||
|
||||
tx_output.mint_proof = MintProof(
|
||||
note.value, note.token_id, note.value_blind,
|
||||
note.token_blind, note.serial, note.coin_blind,
|
||||
output.public, output.spend_hook, output.user_data, self.ec)
|
||||
tx_output.revealed = tx_output.mint_proof.get_revealed()
|
||||
assert tx_output.mint_proof.verify(tx_output.revealed)
|
||||
|
||||
# Is normally encrypted
|
||||
tx_output.enc_note = note
|
||||
tx_output.enc_note.__name__ = "TransactionOutputEncryptedNote"
|
||||
|
||||
tx.outputs.append(tx_output)
|
||||
|
||||
unsigned_tx_data = tx.partial_encode()
|
||||
for (input, info) in zip(tx.clear_inputs, self.clear_inputs):
|
||||
secret = info.signature_secret
|
||||
signature = sign(unsigned_tx_data, secret, self.ec)
|
||||
input.signature = signature
|
||||
for (input, signature_secret) in zip(tx.inputs, signature_secrets):
|
||||
signature = sign(unsigned_tx_data, signature_secret, self.ec)
|
||||
input.signature = signature
|
||||
|
||||
return tx
|
||||
|
||||
# Transaction representing Money::send_payment() function call
|
||||
class SendPaymentTx:
|
||||
|
||||
def __init__(self, ec):
|
||||
self.clear_inputs = []
|
||||
self.inputs = []
|
||||
self.outputs = []
|
||||
|
||||
self.ec = ec
|
||||
|
||||
def partial_encode(self):
|
||||
# There is no cake
|
||||
return b"hello"
|
||||
|
||||
def verify(self):
|
||||
if not self._check_value_commits():
|
||||
return False, "value commits do not match"
|
||||
|
||||
if not self._check_proofs():
|
||||
return False, "proofs failed to verify"
|
||||
|
||||
if not self._verify_token_commitments():
|
||||
return False, "token ID mismatch"
|
||||
|
||||
unsigned_tx_data = self.partial_encode()
|
||||
for input in self.clear_inputs:
|
||||
public = input.signature_public
|
||||
if not verify(unsigned_tx_data, input.signature, public, self.ec):
|
||||
return False
|
||||
for input in self.inputs:
|
||||
public = input.revealed.signature_public
|
||||
if not verify(unsigned_tx_data, input.signature, public, self.ec):
|
||||
return False
|
||||
|
||||
return True, None
|
||||
|
||||
def _check_value_commits(self):
|
||||
valcom_total = (0, 1, 0)
|
||||
|
||||
for input in self.clear_inputs:
|
||||
value_commit = pedersen_encrypt(input.value, input.value_blind,
|
||||
self.ec)
|
||||
valcom_total = self.ec.add(valcom_total, value_commit)
|
||||
for input in self.inputs:
|
||||
value_commit = input.revealed.value_commit
|
||||
valcom_total = self.ec.add(valcom_total, value_commit)
|
||||
for output in self.outputs:
|
||||
v = output.revealed.value_commit
|
||||
value_commit = (v[0], -v[1], v[2])
|
||||
valcom_total = self.ec.add(valcom_total, value_commit)
|
||||
|
||||
return valcom_total == (0, 1, 0)
|
||||
|
||||
def _check_proofs(self):
|
||||
for input in self.inputs:
|
||||
if not input.burn_proof.verify(input.revealed):
|
||||
return False
|
||||
for output in self.outputs:
|
||||
if not output.mint_proof.verify(output.revealed):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _verify_token_commitments(self):
|
||||
assert len(self.outputs) > 0
|
||||
token_commit_value = self.outputs[0].revealed.token_commit
|
||||
for input in self.clear_inputs:
|
||||
token_commit = pedersen_encrypt(input.token_id, input.token_blind,
|
||||
self.ec)
|
||||
if token_commit != token_commit_value:
|
||||
return False
|
||||
for input in self.inputs:
|
||||
if input.revealed.token_commit != token_commit_value:
|
||||
return False
|
||||
for output in self.outputs:
|
||||
if output.revealed.token_commit != token_commit_value:
|
||||
return False
|
||||
return True
|
||||
|
||||
class BurnProof:
|
||||
|
||||
def __init__(self, value, token_id, value_blind, token_blind, serial,
|
||||
coin_blind, secret, spend_hook, user_data, user_data_blind,
|
||||
all_coins, signature_secret, ec):
|
||||
self.value = value
|
||||
self.token_id = token_id
|
||||
self.value_blind = value_blind
|
||||
self.token_blind = token_blind
|
||||
self.serial = serial
|
||||
self.coin_blind = coin_blind
|
||||
self.secret = secret
|
||||
self.spend_hook = spend_hook
|
||||
self.user_data = user_data
|
||||
self.user_data_blind = user_data_blind
|
||||
self.all_coins = all_coins
|
||||
self.signature_secret = signature_secret
|
||||
|
||||
self.ec = ec
|
||||
|
||||
def get_revealed(self):
|
||||
revealed = ClassNamespace()
|
||||
revealed.nullifier = ff_hash(self.ec.p, self.secret, self.serial)
|
||||
|
||||
revealed.value_commit = pedersen_encrypt(
|
||||
self.value, self.value_blind, self.ec
|
||||
)
|
||||
revealed.token_commit = pedersen_encrypt(
|
||||
self.token_id, self.token_blind, self.ec
|
||||
)
|
||||
|
||||
# is_valid_merkle_root()
|
||||
revealed.all_coins = self.all_coins
|
||||
|
||||
revealed.signature_public = self.ec.multiply(self.signature_secret,
|
||||
self.ec.G)
|
||||
|
||||
# This is fully public, no merkle tree or anything
|
||||
revealed.spend_hook = self.spend_hook
|
||||
|
||||
# Re-export user_data field for access by other contracts
|
||||
revealed.enc_user_data = ff_hash(
|
||||
self.ec.p,
|
||||
self.user_data,
|
||||
self.user_data_blind
|
||||
)
|
||||
|
||||
return revealed
|
||||
|
||||
def verify(self, public):
|
||||
revealed = self.get_revealed()
|
||||
|
||||
public_key = self.ec.multiply(self.secret, self.ec.G)
|
||||
coin = ff_hash(
|
||||
self.ec.p,
|
||||
public_key[0],
|
||||
public_key[1],
|
||||
self.value,
|
||||
self.token_id,
|
||||
self.serial,
|
||||
self.coin_blind,
|
||||
self.spend_hook,
|
||||
self.user_data,
|
||||
)
|
||||
# Merkle root check
|
||||
if coin not in self.all_coins:
|
||||
return False
|
||||
|
||||
return all([
|
||||
revealed.nullifier == public.nullifier,
|
||||
revealed.value_commit == public.value_commit,
|
||||
revealed.token_commit == public.token_commit,
|
||||
revealed.all_coins == public.all_coins,
|
||||
revealed.signature_public == public.signature_public,
|
||||
revealed.enc_user_data == public.enc_user_data
|
||||
])
|
||||
|
||||
class MintProof:
|
||||
|
||||
def __init__(self, value, token_id, value_blind, token_blind, serial,
|
||||
coin_blind, public, spend_hook, user_data, ec):
|
||||
self.value = value
|
||||
self.token_id = token_id
|
||||
self.value_blind = value_blind
|
||||
self.token_blind = token_blind
|
||||
self.serial = serial
|
||||
self.coin_blind = coin_blind
|
||||
self.public = public
|
||||
self.spend_hook = spend_hook
|
||||
self.user_data = user_data
|
||||
|
||||
self.ec = ec
|
||||
|
||||
def get_revealed(self):
|
||||
revealed = ClassNamespace()
|
||||
revealed.coin = ff_hash(
|
||||
self.ec.p,
|
||||
self.public[0],
|
||||
self.public[1],
|
||||
self.value,
|
||||
self.token_id,
|
||||
self.serial,
|
||||
self.coin_blind,
|
||||
self.spend_hook,
|
||||
self.user_data
|
||||
)
|
||||
|
||||
revealed.value_commit = pedersen_encrypt(
|
||||
self.value, self.value_blind, self.ec
|
||||
)
|
||||
revealed.token_commit = pedersen_encrypt(
|
||||
self.token_id, self.token_blind, self.ec
|
||||
)
|
||||
|
||||
return revealed
|
||||
|
||||
def verify(self, public):
|
||||
revealed = self.get_revealed()
|
||||
return all([
|
||||
revealed.coin == public.coin,
|
||||
revealed.value_commit == public.value_commit,
|
||||
revealed.token_commit == public.token_commit,
|
||||
])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
@@ -9,11 +10,10 @@ use darkfi::{
|
||||
crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract},
|
||||
contract::{dao, dao::CONTRACT_ID, money},
|
||||
util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
@@ -100,7 +100,7 @@ impl CallDataBase for CallData {
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> core::result::Result<usize, std::io::Error> {
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ pub fn state_transition(
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
@@ -131,17 +131,17 @@ pub fn state_transition(
|
||||
}
|
||||
|
||||
// 3. First item should be a Money::transfer() calldata
|
||||
if parent_tx.func_calls[0].func_id != *money_contract::transfer::FUNC_ID {
|
||||
if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID {
|
||||
return Err(Error::InvalidCallData)
|
||||
}
|
||||
|
||||
let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
|
||||
let money_transfer_call_data =
|
||||
money_transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
|
||||
money_transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
|
||||
let money_transfer_call_data = money_transfer_call_data.unwrap();
|
||||
assert_eq!(
|
||||
money_transfer_call_data.type_id(),
|
||||
TypeId::of::<money_contract::transfer::validate::CallData>()
|
||||
TypeId::of::<money::transfer::validate::CallData>()
|
||||
);
|
||||
|
||||
// 4. Money::transfer() has exactly 2 outputs
|
||||
@@ -168,9 +168,8 @@ pub fn state_transition(
|
||||
}
|
||||
|
||||
// 3. get the ProposalVote from DAO::State
|
||||
let state = states
|
||||
.lookup::<dao_contract::State>(*CONTRACT_ID)
|
||||
.expect("Return type is not of type State");
|
||||
let state =
|
||||
states.lookup::<dao::State>(*CONTRACT_ID).expect("Return type is not of type State");
|
||||
let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
|
||||
|
||||
// 4. check yes_votes_commit is the same as in ProposalVote
|
||||
@@ -193,7 +192,7 @@ pub struct Update {
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states
|
||||
.lookup_mut::<dao_contract::State>(*CONTRACT_ID)
|
||||
.lookup_mut::<dao::State>(*CONTRACT_ID)
|
||||
.expect("Return type is not of type State");
|
||||
state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
use log::debug;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use halo2_proofs::circuit::Value;
|
||||
use log::debug;
|
||||
use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
@@ -14,7 +13,7 @@ use darkfi::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::dao_contract::{
|
||||
contract::dao::{
|
||||
exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
|
||||
},
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
@@ -40,7 +39,6 @@ pub struct Builder {
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
debug!(target: "dao_contract::exec::wallet::Builder", "build()");
|
||||
debug!(target: "dao_contract::exec::wallet", "proposalserial{:?}", self.proposal.serial);
|
||||
let mut proofs = vec![];
|
||||
|
||||
let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
|
||||
@@ -100,7 +98,7 @@ impl Builder {
|
||||
self.proposal.token_id,
|
||||
self.dao_serial,
|
||||
self.hook_dao_exec,
|
||||
proposal_bulla,
|
||||
dao_bulla,
|
||||
self.dao_coin_blind,
|
||||
]);
|
||||
|
||||
@@ -123,6 +121,7 @@ impl Builder {
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
//
|
||||
// proposal params
|
||||
Witness::Base(Value::known(*proposal_dest_coords.x())),
|
||||
Witness::Base(Value::known(*proposal_dest_coords.y())),
|
||||
@@ -173,7 +172,6 @@ impl Builder {
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()");
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::exec() proving error!)");
|
||||
@@ -4,7 +4,7 @@ use darkfi::crypto::{keypair::PublicKey, types::DrkCircuitField};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::dao_contract::{DaoBulla, State, CONTRACT_ID},
|
||||
contract::dao::{DaoBulla, State, CONTRACT_ID},
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn state_transition(
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
@@ -65,7 +65,7 @@ impl CallDataBase for CallData {
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> core::result::Result<usize, std::io::Error> {
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
use halo2_proofs::circuit::Value;
|
||||
use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
keypair::{PublicKey, SecretKey},
|
||||
@@ -6,12 +10,9 @@ use darkfi::{
|
||||
},
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::{
|
||||
contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
|
||||
contract::dao::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID},
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
170
example/dao/src/contract/dao/propose/validate.rs
Normal file
170
example/dao/src/contract/dao/propose/validate.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use log::error;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use darkfi::{
|
||||
crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
// used for debugging
|
||||
// const TARGET: &str = "dao_contract::propose::validate::state_transition()";
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid input merkle root")]
|
||||
InvalidInputMerkleRoot,
|
||||
|
||||
#[error("Invalid DAO merkle root")]
|
||||
InvalidDaoMerkleRoot,
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub header: Header,
|
||||
pub inputs: Vec<Input>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut zk_publics = Vec::new();
|
||||
let mut total_funds_commit = pallas::Point::identity();
|
||||
|
||||
assert!(self.inputs.len() > 0, "inputs length cannot be zero");
|
||||
for input in &self.inputs {
|
||||
total_funds_commit += input.value_commit;
|
||||
let value_coords = input.value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-propose-burn".to_string(),
|
||||
vec![
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
self.header.token_commit,
|
||||
input.merkle_root.0,
|
||||
*sigpub_coords.x(),
|
||||
*sigpub_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
|
||||
zk_publics.push((
|
||||
"dao-propose-main".to_string(),
|
||||
vec![
|
||||
self.header.token_commit,
|
||||
self.header.dao_merkle_root.0,
|
||||
self.header.proposal_bulla,
|
||||
*total_funds_coords.x(),
|
||||
*total_funds_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
zk_publics
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = vec![];
|
||||
for input in self.inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Header {
|
||||
pub dao_merkle_root: MerkleNode,
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
pub value_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
// Check the merkle roots for the input coins are valid
|
||||
for input in &call_data.inputs {
|
||||
let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
|
||||
if !money_state.is_valid_merkle(&input.merkle_root) {
|
||||
return Err(Error::InvalidInputMerkleRoot)
|
||||
}
|
||||
}
|
||||
|
||||
let state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
|
||||
// Is the DAO bulla generated in the ZK proof valid
|
||||
if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) {
|
||||
return Err(Error::InvalidDaoMerkleRoot)
|
||||
}
|
||||
|
||||
// TODO: look at gov tokens avoid using already spent ones
|
||||
// Need to spend original coin and generate 2 nullifiers?
|
||||
|
||||
Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla }))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
pub proposal_bulla: pallas::Base,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
state.add_proposal_bulla(self.proposal_bulla);
|
||||
}
|
||||
}
|
||||
272
example/dao/src/contract/dao/propose/wallet.rs
Normal file
272
example/dao/src/contract/dao/propose/wallet.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use halo2_proofs::circuit::Value;
|
||||
use incrementalmerkletree::Hashable;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{ff::Field, Curve},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
keypair::{PublicKey, SecretKey},
|
||||
merkle_node::MerkleNode,
|
||||
util::{pedersen_commitment_u64, poseidon_hash},
|
||||
Proof,
|
||||
},
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao::{
|
||||
mint::wallet::DaoParams,
|
||||
propose::validate::{CallData, Header, Input},
|
||||
CONTRACT_ID,
|
||||
},
|
||||
money,
|
||||
},
|
||||
note,
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub proposal: Proposal,
|
||||
}
|
||||
|
||||
pub struct BuilderInput {
|
||||
pub secret: SecretKey,
|
||||
pub note: money::transfer::wallet::Note,
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable, Clone)]
|
||||
pub struct Proposal {
|
||||
pub dest: PublicKey,
|
||||
pub amount: u64,
|
||||
pub serial: pallas::Base,
|
||||
pub token_id: pallas::Base,
|
||||
pub blind: pallas::Base,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub inputs: Vec<BuilderInput>,
|
||||
pub proposal: Proposal,
|
||||
pub dao: DaoParams,
|
||||
pub dao_leaf_position: incrementalmerkletree::Position,
|
||||
pub dao_merkle_path: Vec<MerkleNode>,
|
||||
pub dao_merkle_root: MerkleNode,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
let mut proofs = vec![];
|
||||
|
||||
let gov_token_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut total_funds = 0;
|
||||
let mut total_funds_blinds = pallas::Scalar::from(0);
|
||||
|
||||
for input in self.inputs {
|
||||
let funds_blind = pallas::Scalar::random(&mut OsRng);
|
||||
total_funds += input.note.value;
|
||||
total_funds_blinds += funds_blind;
|
||||
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note;
|
||||
let leaf_pos: u64 = input.leaf_position.into();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(input.secret.0)),
|
||||
Witness::Base(Value::known(note.serial)),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(note.value))),
|
||||
Witness::Base(Value::known(note.token_id)),
|
||||
Witness::Base(Value::known(note.coin_blind)),
|
||||
Witness::Scalar(Value::known(funds_blind)),
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
|
||||
Witness::Base(Value::known(input.signature_secret.0)),
|
||||
];
|
||||
|
||||
let public_key = PublicKey::from_secret(input.secret);
|
||||
let coords = public_key.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let coin = poseidon_hash::<8>([
|
||||
*coords.x(),
|
||||
*coords.y(),
|
||||
pallas::Base::from(note.value),
|
||||
note.token_id,
|
||||
note.serial,
|
||||
pallas::Base::from(0),
|
||||
pallas::Base::from(0),
|
||||
note.coin_blind,
|
||||
]);
|
||||
|
||||
let merkle_root = {
|
||||
let position: u64 = input.leaf_position.into();
|
||||
let mut current = MerkleNode(coin);
|
||||
for (level, sibling) in input.merkle_path.iter().enumerate() {
|
||||
let level = level as u8;
|
||||
current = if position & (1 << level) == 0 {
|
||||
MerkleNode::combine(level.into(), ¤t, sibling)
|
||||
} else {
|
||||
MerkleNode::combine(level.into(), sibling, ¤t)
|
||||
};
|
||||
}
|
||||
current
|
||||
};
|
||||
|
||||
let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
|
||||
assert_eq!(self.dao.gov_token_id, note.token_id);
|
||||
|
||||
let value_commit = pedersen_commitment_u64(note.value, funds_blind);
|
||||
let value_coords = value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let public_inputs = vec![
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
token_commit,
|
||||
merkle_root.0,
|
||||
*sigpub_coords.x(),
|
||||
*sigpub_coords.y(),
|
||||
];
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::propose() proving error!");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let input = Input { value_commit, merkle_root, signature_public };
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds);
|
||||
let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
|
||||
let total_funds = pallas::Base::from(total_funds);
|
||||
|
||||
let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
|
||||
|
||||
let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
|
||||
let proposal_dest_x = *proposal_dest_coords.x();
|
||||
let proposal_dest_y = *proposal_dest_coords.y();
|
||||
|
||||
let proposal_amount = pallas::Base::from(self.proposal.amount);
|
||||
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao.quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
|
||||
|
||||
let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.dao.gov_token_id,
|
||||
*dao_pubkey_coords.x(),
|
||||
*dao_pubkey_coords.y(),
|
||||
self.dao.bulla_blind,
|
||||
]);
|
||||
|
||||
let dao_leaf_position: u64 = self.dao_leaf_position.into();
|
||||
|
||||
let proposal_bulla = poseidon_hash::<8>([
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
self.proposal.serial,
|
||||
self.proposal.token_id,
|
||||
dao_bulla,
|
||||
self.proposal.blind,
|
||||
// @tmp-workaround
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
let prover_witnesses = vec![
|
||||
// Proposers total number of gov tokens
|
||||
Witness::Base(Value::known(total_funds)),
|
||||
Witness::Scalar(Value::known(total_funds_blinds)),
|
||||
// Used for blinding exported gov token ID
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
// proposal params
|
||||
Witness::Base(Value::known(proposal_dest_x)),
|
||||
Witness::Base(Value::known(proposal_dest_y)),
|
||||
Witness::Base(Value::known(proposal_amount)),
|
||||
Witness::Base(Value::known(self.proposal.serial)),
|
||||
Witness::Base(Value::known(self.proposal.token_id)),
|
||||
Witness::Base(Value::known(self.proposal.blind)),
|
||||
// DAO params
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.dao.gov_token_id)),
|
||||
Witness::Base(Value::known(*dao_pubkey_coords.x())),
|
||||
Witness::Base(Value::known(*dao_pubkey_coords.y())),
|
||||
Witness::Base(Value::known(self.dao.bulla_blind)),
|
||||
Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
|
||||
];
|
||||
let public_inputs = vec![
|
||||
token_commit,
|
||||
self.dao_merkle_root.0,
|
||||
proposal_bulla,
|
||||
*total_funds_coords.x(),
|
||||
*total_funds_coords.y(),
|
||||
];
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::propose() proving error!");
|
||||
proofs.push(main_proof);
|
||||
|
||||
let note = Note { proposal: self.proposal };
|
||||
let enc_note = note::encrypt(¬e, &self.dao.public_key).unwrap();
|
||||
let header = Header {
|
||||
dao_merkle_root: self.dao_merkle_root,
|
||||
proposal_bulla,
|
||||
token_commit,
|
||||
enc_note,
|
||||
};
|
||||
|
||||
let call_data = CallData { header, inputs };
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
97
example/dao/src/contract/dao/state.rs
Normal file
97
example/dao/src/contract/dao/state.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use std::{any::Any, collections::HashMap};
|
||||
|
||||
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
|
||||
use pasta_curves::{group::Group, pallas};
|
||||
|
||||
use darkfi::crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::util::HashableBase;
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoBulla(pub pallas::Base);
|
||||
|
||||
type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
|
||||
|
||||
pub struct ProposalVotes {
|
||||
// TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit'
|
||||
/// Weighted vote commit
|
||||
pub yes_votes_commit: pallas::Point,
|
||||
/// All value staked in the vote
|
||||
pub all_votes_commit: pallas::Point,
|
||||
/// Vote nullifiers
|
||||
pub vote_nulls: Vec<Nullifier>,
|
||||
}
|
||||
|
||||
impl ProposalVotes {
|
||||
pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
self.vote_nulls.iter().any(|n| n == nullifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// This DAO state is for all DAOs on the network. There should only be a single instance.
|
||||
pub struct State {
|
||||
dao_bullas: Vec<DaoBulla>,
|
||||
pub dao_tree: MerkleTree,
|
||||
pub dao_roots: Vec<MerkleNode>,
|
||||
|
||||
//proposal_bullas: Vec<pallas::Base>,
|
||||
pub proposal_tree: MerkleTree,
|
||||
pub proposal_roots: Vec<MerkleNode>,
|
||||
pub proposal_votes: HashMap<HashableBase, ProposalVotes>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Box<dyn Any + Send> {
|
||||
Box::new(Self {
|
||||
dao_bullas: Vec::new(),
|
||||
dao_tree: MerkleTree::new(100),
|
||||
dao_roots: Vec::new(),
|
||||
//proposal_bullas: Vec::new(),
|
||||
proposal_tree: MerkleTree::new(100),
|
||||
proposal_roots: Vec::new(),
|
||||
proposal_votes: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_dao_bulla(&mut self, bulla: DaoBulla) {
|
||||
let node = MerkleNode(bulla.0);
|
||||
self.dao_bullas.push(bulla);
|
||||
self.dao_tree.append(&node);
|
||||
self.dao_roots.push(self.dao_tree.root(0).unwrap());
|
||||
}
|
||||
|
||||
pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) {
|
||||
let node = MerkleNode(bulla);
|
||||
//self.proposal_bullas.push(bulla);
|
||||
self.proposal_tree.append(&node);
|
||||
self.proposal_roots.push(self.proposal_tree.root(0).unwrap());
|
||||
self.proposal_votes.insert(
|
||||
HashableBase(bulla),
|
||||
ProposalVotes {
|
||||
yes_votes_commit: pallas::Point::identity(),
|
||||
all_votes_commit: pallas::Point::identity(),
|
||||
vote_nulls: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> {
|
||||
self.proposal_votes.get(&HashableBase(proposal_bulla))
|
||||
}
|
||||
pub fn lookup_proposal_votes_mut(
|
||||
&mut self,
|
||||
proposal_bulla: pallas::Base,
|
||||
) -> Option<&mut ProposalVotes> {
|
||||
self.proposal_votes.get_mut(&HashableBase(proposal_bulla))
|
||||
}
|
||||
|
||||
pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool {
|
||||
self.dao_roots.iter().any(|m| m == root)
|
||||
}
|
||||
|
||||
// TODO: This never gets called.
|
||||
pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool {
|
||||
self.proposal_roots.iter().any(|m| m == root)
|
||||
}
|
||||
}
|
||||
205
example/dao/src/contract/dao/vote/validate.rs
Normal file
205
example/dao/src/contract/dao/vote/validate.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use log::error;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, types::DrkCircuitField,
|
||||
},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid proposal")]
|
||||
InvalidProposal,
|
||||
|
||||
#[error("Voting with already spent coinage")]
|
||||
SpentCoin,
|
||||
|
||||
#[error("Double voting")]
|
||||
DoubleVote,
|
||||
|
||||
#[error("Invalid input merkle root")]
|
||||
InvalidInputMerkleRoot,
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub header: Header,
|
||||
pub inputs: Vec<Input>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut zk_publics = Vec::new();
|
||||
let mut all_votes_commit = pallas::Point::identity();
|
||||
|
||||
assert!(self.inputs.len() > 0, "inputs length cannot be zero");
|
||||
for input in &self.inputs {
|
||||
all_votes_commit += input.vote_commit;
|
||||
let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-vote-burn".to_string(),
|
||||
vec![
|
||||
input.nullifier.0,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
self.header.token_commit,
|
||||
input.merkle_root.0,
|
||||
*sigpub_coords.x(),
|
||||
*sigpub_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-vote-main".to_string(),
|
||||
vec![
|
||||
self.header.token_commit,
|
||||
self.header.proposal_bulla,
|
||||
*yes_vote_commit_coords.x(),
|
||||
*yes_vote_commit_coords.y(),
|
||||
*vote_commit_coords.x(),
|
||||
*vote_commit_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
zk_publics
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = vec![];
|
||||
for input in self.inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Header {
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
pub nullifier: Nullifier,
|
||||
pub vote_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
let dao_state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
|
||||
// Check proposal_bulla exists
|
||||
let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla);
|
||||
if votes_info.is_none() {
|
||||
return Err(Error::InvalidProposal)
|
||||
}
|
||||
let votes_info = votes_info.unwrap();
|
||||
|
||||
// Check the merkle roots for the input coins are valid
|
||||
let mut vote_nulls = Vec::new();
|
||||
let mut all_vote_commit = pallas::Point::identity();
|
||||
for input in &call_data.inputs {
|
||||
let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
|
||||
if !money_state.is_valid_merkle(&input.merkle_root) {
|
||||
return Err(Error::InvalidInputMerkleRoot)
|
||||
}
|
||||
|
||||
if money_state.nullifier_exists(&input.nullifier) {
|
||||
return Err(Error::SpentCoin)
|
||||
}
|
||||
|
||||
if votes_info.nullifier_exists(&input.nullifier) {
|
||||
return Err(Error::DoubleVote)
|
||||
}
|
||||
|
||||
all_vote_commit += input.vote_commit;
|
||||
|
||||
vote_nulls.push(input.nullifier);
|
||||
}
|
||||
|
||||
Ok(Box::new(Update {
|
||||
proposal_bulla: call_data.header.proposal_bulla,
|
||||
vote_nulls,
|
||||
yes_vote_commit: call_data.header.yes_vote_commit,
|
||||
all_vote_commit,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
proposal_bulla: pallas::Base,
|
||||
vote_nulls: Vec<Nullifier>,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub all_vote_commit: pallas::Point,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap();
|
||||
votes_info.yes_votes_commit += self.yes_vote_commit;
|
||||
votes_info.all_votes_commit += self.all_vote_commit;
|
||||
votes_info.vote_nulls.append(&mut self.vote_nulls);
|
||||
}
|
||||
}
|
||||
292
example/dao/src/contract/dao/vote/wallet.rs
Normal file
292
example/dao/src/contract/dao/vote/wallet.rs
Normal file
@@ -0,0 +1,292 @@
|
||||
use halo2_proofs::circuit::Value;
|
||||
use incrementalmerkletree::Hashable;
|
||||
use log::debug;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{ff::Field, Curve},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
keypair::{Keypair, PublicKey, SecretKey},
|
||||
merkle_node::MerkleNode,
|
||||
nullifier::Nullifier,
|
||||
util::{pedersen_commitment_u64, poseidon_hash},
|
||||
Proof,
|
||||
},
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao::{
|
||||
mint::wallet::DaoParams,
|
||||
propose::wallet::Proposal,
|
||||
vote::validate::{CallData, Header, Input},
|
||||
CONTRACT_ID,
|
||||
},
|
||||
money,
|
||||
},
|
||||
note,
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub vote: Vote,
|
||||
pub vote_value: u64,
|
||||
pub vote_value_blind: pallas::Scalar,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Vote {
|
||||
pub vote_option: bool,
|
||||
pub vote_option_blind: pallas::Scalar,
|
||||
}
|
||||
|
||||
pub struct BuilderInput {
|
||||
pub secret: SecretKey,
|
||||
pub note: money::transfer::wallet::Note,
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
// TODO: should be token locking voting?
|
||||
// Inside ZKproof, check proposal is correct.
|
||||
pub struct Builder {
|
||||
pub inputs: Vec<BuilderInput>,
|
||||
pub vote: Vote,
|
||||
pub vote_keypair: Keypair,
|
||||
pub proposal: Proposal,
|
||||
pub dao: DaoParams,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "build()");
|
||||
let mut proofs = vec![];
|
||||
|
||||
let gov_token_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut vote_value = 0;
|
||||
let mut vote_value_blind = pallas::Scalar::from(0);
|
||||
|
||||
for input in self.inputs {
|
||||
let value_blind = pallas::Scalar::random(&mut OsRng);
|
||||
|
||||
vote_value += input.note.value;
|
||||
vote_value_blind += value_blind;
|
||||
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap();
|
||||
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note;
|
||||
let leaf_pos: u64 = input.leaf_position.into();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(input.secret.0)),
|
||||
Witness::Base(Value::known(note.serial)),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(note.value))),
|
||||
Witness::Base(Value::known(note.token_id)),
|
||||
Witness::Base(Value::known(note.coin_blind)),
|
||||
Witness::Scalar(Value::known(vote_value_blind)),
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
|
||||
Witness::Base(Value::known(input.signature_secret.0)),
|
||||
];
|
||||
|
||||
let public_key = PublicKey::from_secret(input.secret);
|
||||
let coords = public_key.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let coin = poseidon_hash::<8>([
|
||||
*coords.x(),
|
||||
*coords.y(),
|
||||
pallas::Base::from(note.value),
|
||||
note.token_id,
|
||||
note.serial,
|
||||
pallas::Base::from(0),
|
||||
pallas::Base::from(0),
|
||||
note.coin_blind,
|
||||
]);
|
||||
|
||||
let merkle_root = {
|
||||
let position: u64 = input.leaf_position.into();
|
||||
let mut current = MerkleNode(coin);
|
||||
for (level, sibling) in input.merkle_path.iter().enumerate() {
|
||||
let level = level as u8;
|
||||
current = if position & (1 << level) == 0 {
|
||||
MerkleNode::combine(level.into(), ¤t, sibling)
|
||||
} else {
|
||||
MerkleNode::combine(level.into(), sibling, ¤t)
|
||||
};
|
||||
}
|
||||
current
|
||||
};
|
||||
|
||||
let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]);
|
||||
assert_eq!(self.dao.gov_token_id, note.token_id);
|
||||
|
||||
let nullifier = poseidon_hash::<2>([input.secret.0, note.serial]);
|
||||
|
||||
let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
|
||||
let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let public_inputs = vec![
|
||||
nullifier,
|
||||
*vote_commit_coords.x(),
|
||||
*vote_commit_coords.y(),
|
||||
token_commit,
|
||||
merkle_root.0,
|
||||
*sigpub_coords.x(),
|
||||
*sigpub_coords.y(),
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
let proving_key = &zk_info.proving_key;
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::vote() proving error!");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let input = Input {
|
||||
nullifier: Nullifier(nullifier),
|
||||
vote_commit,
|
||||
merkle_root,
|
||||
signature_public,
|
||||
};
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]);
|
||||
|
||||
let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let proposal_amount = pallas::Base::from(self.proposal.amount);
|
||||
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao.quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
|
||||
|
||||
let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap();
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.dao.gov_token_id,
|
||||
*dao_pubkey_coords.x(),
|
||||
*dao_pubkey_coords.y(),
|
||||
self.dao.bulla_blind,
|
||||
]);
|
||||
|
||||
let proposal_bulla = poseidon_hash::<8>([
|
||||
*proposal_dest_coords.x(),
|
||||
*proposal_dest_coords.y(),
|
||||
proposal_amount,
|
||||
self.proposal.serial,
|
||||
self.proposal.token_id,
|
||||
dao_bulla,
|
||||
self.proposal.blind,
|
||||
// @tmp-workaround
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let vote_option = self.vote.vote_option as u64;
|
||||
assert!(vote_option == 0 || vote_option == 1);
|
||||
|
||||
let yes_vote_commit =
|
||||
pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind);
|
||||
let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind);
|
||||
let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
// proposal params
|
||||
Witness::Base(Value::known(*proposal_dest_coords.x())),
|
||||
Witness::Base(Value::known(*proposal_dest_coords.y())),
|
||||
Witness::Base(Value::known(proposal_amount)),
|
||||
Witness::Base(Value::known(self.proposal.serial)),
|
||||
Witness::Base(Value::known(self.proposal.token_id)),
|
||||
Witness::Base(Value::known(self.proposal.blind)),
|
||||
// DAO params
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.dao.gov_token_id)),
|
||||
Witness::Base(Value::known(*dao_pubkey_coords.x())),
|
||||
Witness::Base(Value::known(*dao_pubkey_coords.y())),
|
||||
Witness::Base(Value::known(self.dao.bulla_blind)),
|
||||
// Vote
|
||||
Witness::Base(Value::known(pallas::Base::from(vote_option))),
|
||||
Witness::Scalar(Value::known(self.vote.vote_option_blind)),
|
||||
// Total number of gov tokens allocated
|
||||
Witness::Base(Value::known(pallas::Base::from(vote_value))),
|
||||
Witness::Scalar(Value::known(vote_value_blind)),
|
||||
// gov token
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
];
|
||||
|
||||
let public_inputs = vec![
|
||||
token_commit,
|
||||
proposal_bulla,
|
||||
// this should be a value commit??
|
||||
*yes_vote_commit_coords.x(),
|
||||
*yes_vote_commit_coords.y(),
|
||||
*all_vote_commit_coords.x(),
|
||||
*all_vote_commit_coords.y(),
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
|
||||
let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::vote() proving error!");
|
||||
proofs.push(main_proof);
|
||||
|
||||
let note = Note { vote: self.vote, vote_value, vote_value_blind };
|
||||
let enc_note = note::encrypt(¬e, &self.vote_keypair.public).unwrap();
|
||||
|
||||
let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note };
|
||||
|
||||
let call_data = CallData { header, inputs };
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{keypair::PublicKey, types::DrkCircuitField},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::example_contract::{state::State, CONTRACT_ID},
|
||||
contract::example::{state::State, CONTRACT_ID},
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
@@ -19,7 +19,6 @@ type Result<T> = std::result::Result<T, Error>;
|
||||
pub enum Error {
|
||||
#[error("ValueExists")]
|
||||
ValueExists,
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
@@ -52,7 +51,7 @@ impl CallDataBase for CallData {
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> core::result::Result<usize, std::io::Error> {
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
@@ -65,7 +64,7 @@ pub fn state_transition(
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
@@ -13,7 +13,7 @@ use darkfi::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::example_contract::{foo::validate::CallData, CONTRACT_ID},
|
||||
contract::example::{foo::validate::CallData, CONTRACT_ID},
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
@@ -7,14 +7,14 @@ pub struct State {
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Box<dyn Any> {
|
||||
pub fn new() -> Box<dyn Any + Send> {
|
||||
Box::new(Self { public_values: Vec::new() })
|
||||
}
|
||||
|
||||
pub fn add_public_value(&mut self, public_value: pallas::Base) {
|
||||
self.public_values.push(public_value)
|
||||
}
|
||||
|
||||
//
|
||||
pub fn public_exists(&self, public_value: &pallas::Base) -> bool {
|
||||
self.public_values.iter().any(|v| v == public_value)
|
||||
}
|
||||
3
example/dao/src/contract/mod.rs
Normal file
3
example/dao/src/contract/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod dao;
|
||||
pub mod example;
|
||||
pub mod money;
|
||||
116
example/dao/src/contract/money/state.rs
Normal file
116
example/dao/src/contract/money/state.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
|
||||
|
||||
use darkfi::crypto::{
|
||||
coin::Coin,
|
||||
constants::MERKLE_DEPTH,
|
||||
keypair::{PublicKey, SecretKey},
|
||||
merkle_node::MerkleNode,
|
||||
nullifier::Nullifier,
|
||||
};
|
||||
|
||||
use super::transfer;
|
||||
use crate::note::EncryptedNote2;
|
||||
|
||||
type MerkleTree = BridgeTree<MerkleNode, MERKLE_DEPTH>;
|
||||
|
||||
pub struct OwnCoin {
|
||||
pub coin: Coin,
|
||||
pub note: transfer::wallet::Note,
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
}
|
||||
|
||||
pub struct WalletCache {
|
||||
// Normally this would be a HashMap, but SecretKey is not Hash-able
|
||||
// TODO: This can be HashableBase
|
||||
cache: Vec<(SecretKey, Vec<OwnCoin>)>,
|
||||
}
|
||||
|
||||
impl WalletCache {
|
||||
pub fn new() -> Self {
|
||||
Self { cache: Vec::new() }
|
||||
}
|
||||
|
||||
/// Must be called at the start to begin tracking received coins for this secret.
|
||||
pub fn track(&mut self, secret: SecretKey) {
|
||||
self.cache.push((secret, Vec::new()));
|
||||
}
|
||||
|
||||
/// Get all coins received by this secret key
|
||||
/// track() must be called on this secret before calling this or the function will panic.
|
||||
pub fn get_received(&mut self, secret: &SecretKey) -> Vec<OwnCoin> {
|
||||
for (other_secret, own_coins) in self.cache.iter_mut() {
|
||||
if *secret == *other_secret {
|
||||
// clear own_coins vec, and return current contents
|
||||
return std::mem::replace(own_coins, Vec::new())
|
||||
}
|
||||
}
|
||||
panic!("you forget to track() this secret!");
|
||||
}
|
||||
|
||||
pub fn try_decrypt_note(
|
||||
&mut self,
|
||||
coin: Coin,
|
||||
ciphertext: EncryptedNote2,
|
||||
tree: &mut MerkleTree,
|
||||
) {
|
||||
// Loop through all our secret keys...
|
||||
for (secret, own_coins) in self.cache.iter_mut() {
|
||||
// .. attempt to decrypt the note ...
|
||||
if let Ok(note) = ciphertext.decrypt(secret) {
|
||||
let leaf_position = tree.witness().expect("coin should be in tree");
|
||||
own_coins.push(OwnCoin { coin, note, leaf_position });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state machine, held in memory.
|
||||
pub struct State {
|
||||
/// The entire Merkle tree state
|
||||
pub tree: MerkleTree,
|
||||
/// List of all previous and the current Merkle roots.
|
||||
/// This is the hashed value of all the children.
|
||||
pub merkle_roots: Vec<MerkleNode>,
|
||||
/// Nullifiers prevent double spending
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
|
||||
/// Public key of the cashier
|
||||
pub cashier_signature_public: PublicKey,
|
||||
|
||||
/// Public key of the faucet
|
||||
pub faucet_signature_public: PublicKey,
|
||||
|
||||
pub wallet_cache: WalletCache,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(
|
||||
cashier_signature_public: PublicKey,
|
||||
faucet_signature_public: PublicKey,
|
||||
) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
tree: MerkleTree::new(100),
|
||||
merkle_roots: vec![],
|
||||
nullifiers: vec![],
|
||||
cashier_signature_public,
|
||||
faucet_signature_public,
|
||||
wallet_cache: WalletCache::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_valid_cashier_public_key(&self, public: &PublicKey) -> bool {
|
||||
public == &self.cashier_signature_public
|
||||
}
|
||||
|
||||
pub fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool {
|
||||
public == &self.faucet_signature_public
|
||||
}
|
||||
|
||||
pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
||||
self.merkle_roots.iter().any(|m| m == merkle_root)
|
||||
}
|
||||
|
||||
pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
self.nullifiers.iter().any(|n| n == nullifier)
|
||||
}
|
||||
}
|
||||
375
example/dao/src/contract/money/transfer/validate.rs
Normal file
375
example/dao/src/contract/money/transfer/validate.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use incrementalmerkletree::Tree;
|
||||
use log::{debug, error};
|
||||
use pasta_curves::{group::Group, pallas};
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
coin::Coin,
|
||||
keypair::PublicKey,
|
||||
merkle_node::MerkleNode,
|
||||
nullifier::Nullifier,
|
||||
types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit},
|
||||
util::{pedersen_commitment_base, pedersen_commitment_u64},
|
||||
BurnRevealedValues, MintRevealedValues,
|
||||
},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao,
|
||||
money::{state::State, CONTRACT_ID},
|
||||
},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
const TARGET: &str = "money_contract::transfer::validate::state_transition()";
|
||||
|
||||
/// A struct representing a state update.
|
||||
/// This gets applied on top of an existing state.
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
/// All nullifiers in a transaction
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
/// All coins in a transaction
|
||||
pub coins: Vec<Coin>,
|
||||
/// All encrypted notes in a transaction
|
||||
pub enc_notes: Vec<EncryptedNote2>,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
|
||||
|
||||
// Extend our list of nullifiers with the ones from the update
|
||||
state.nullifiers.append(&mut self.nullifiers);
|
||||
|
||||
//// Update merkle tree and witnesses
|
||||
for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) {
|
||||
// Add the new coins to the Merkle tree
|
||||
let node = MerkleNode(coin.0);
|
||||
state.tree.append(&node);
|
||||
|
||||
// Keep track of all Merkle roots that have existed
|
||||
state.merkle_roots.push(state.tree.root(0).unwrap());
|
||||
|
||||
state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
// Check the public keys in the clear inputs to see if they're coming
|
||||
// from a valid cashier or faucet.
|
||||
debug!(target: TARGET, "Iterate clear_inputs");
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
let state = states.lookup::<State>(*CONTRACT_ID).expect("Return type is not of type State");
|
||||
|
||||
// Code goes here
|
||||
for (i, input) in call_data.clear_inputs.iter().enumerate() {
|
||||
let pk = &input.signature_public;
|
||||
// TODO: this depends on the token ID
|
||||
if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) {
|
||||
error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i)))
|
||||
}
|
||||
}
|
||||
|
||||
// Nullifiers in the transaction
|
||||
let mut nullifiers = Vec::with_capacity(call_data.inputs.len());
|
||||
|
||||
debug!(target: TARGET, "Iterate inputs");
|
||||
for (i, input) in call_data.inputs.iter().enumerate() {
|
||||
let merkle = &input.revealed.merkle_root;
|
||||
|
||||
// The Merkle root is used to know whether this is a coin that
|
||||
// existed in a previous state.
|
||||
if !state.is_valid_merkle(merkle) {
|
||||
error!(target: TARGET, "Invalid Merkle root (input {})", i);
|
||||
debug!(target: TARGET, "root: {:?}", merkle);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i)))
|
||||
}
|
||||
|
||||
// Check the spend_hook is satisfied
|
||||
// The spend_hook says a coin must invoke another contract function when being spent
|
||||
// If the value is set, then we check the function call exists
|
||||
let spend_hook = &input.revealed.spend_hook;
|
||||
if spend_hook != &pallas::Base::from(0) {
|
||||
// spend_hook is set so we enforce the rules
|
||||
let mut is_found = false;
|
||||
for (i, func_call) in parent_tx.func_calls.iter().enumerate() {
|
||||
// Skip current func_call
|
||||
if i == func_call_index {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: we need to change these to pallas::Base
|
||||
// temporary workaround for now
|
||||
// if func_call.func_id == spend_hook ...
|
||||
if func_call.func_id == *dao::exec::FUNC_ID {
|
||||
is_found = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
if !is_found {
|
||||
return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied))
|
||||
}
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist.
|
||||
// It is the double-spend protection.
|
||||
let nullifier = &input.revealed.nullifier;
|
||||
if state.nullifier_exists(nullifier) ||
|
||||
(1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1]))
|
||||
{
|
||||
error!(target: TARGET, "Duplicate nullifier found (input {})", i);
|
||||
debug!(target: TARGET, "nullifier: {:?}", nullifier);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i)))
|
||||
}
|
||||
|
||||
nullifiers.push(input.revealed.nullifier);
|
||||
}
|
||||
|
||||
debug!(target: TARGET, "Verifying call data");
|
||||
match call_data.verify() {
|
||||
Ok(()) => {
|
||||
debug!(target: TARGET, "Verified successfully")
|
||||
}
|
||||
Err(e) => {
|
||||
error!(target: TARGET, "Failed verifying zk proofs: {}", e);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction
|
||||
let mut coins = Vec::with_capacity(call_data.outputs.len());
|
||||
let mut enc_notes = Vec::with_capacity(call_data.outputs.len());
|
||||
|
||||
for output in &call_data.outputs {
|
||||
// Gather all the coins
|
||||
coins.push(output.revealed.coin);
|
||||
enc_notes.push(output.enc_note.clone());
|
||||
}
|
||||
|
||||
Ok(Box::new(Update { nullifiers, coins, enc_notes }))
|
||||
}
|
||||
|
||||
/// A DarkFi transaction
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
/// Clear inputs
|
||||
pub clear_inputs: Vec<ClearInput>,
|
||||
/// Anonymous inputs
|
||||
pub inputs: Vec<Input>,
|
||||
/// Anonymous outputs
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut public_values = Vec::new();
|
||||
for input in &self.inputs {
|
||||
public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs()));
|
||||
}
|
||||
for output in &self.outputs {
|
||||
public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs()));
|
||||
}
|
||||
public_values
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = Vec::new();
|
||||
for input in self.clear_inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
impl CallData {
|
||||
/// Verify the transaction
|
||||
pub fn verify(&self) -> VerifyResult<()> {
|
||||
// must have minimum 1 clear or anon input, and 1 output
|
||||
if self.clear_inputs.len() + self.inputs.len() == 0 {
|
||||
error!("tx::verify(): Missing inputs");
|
||||
return Err(VerifyFailed::LackingInputs)
|
||||
}
|
||||
if self.outputs.len() == 0 {
|
||||
error!("tx::verify(): Missing outputs");
|
||||
return Err(VerifyFailed::LackingOutputs)
|
||||
}
|
||||
|
||||
// Accumulator for the value commitments
|
||||
let mut valcom_total = DrkValueCommit::identity();
|
||||
|
||||
// Add values from the clear inputs
|
||||
for input in &self.clear_inputs {
|
||||
valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
||||
}
|
||||
// Add values from the inputs
|
||||
for input in &self.inputs {
|
||||
valcom_total += &input.revealed.value_commit;
|
||||
}
|
||||
// Subtract values from the outputs
|
||||
for output in &self.outputs {
|
||||
valcom_total -= &output.revealed.value_commit;
|
||||
}
|
||||
|
||||
// If the accumulator is not back in its initial state,
|
||||
// there's a value mismatch.
|
||||
if valcom_total != DrkValueCommit::identity() {
|
||||
error!("tx::verify(): Missing funds");
|
||||
return Err(VerifyFailed::MissingFunds)
|
||||
}
|
||||
|
||||
// Verify that the token commitments match
|
||||
if !self.verify_token_commitments() {
|
||||
error!("tx::verify(): Token ID mismatch");
|
||||
return Err(VerifyFailed::TokenMismatch)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_token_commitments(&self) -> bool {
|
||||
assert_ne!(self.outputs.len(), 0);
|
||||
let token_commit_value = self.outputs[0].revealed.token_commit;
|
||||
|
||||
let mut failed =
|
||||
self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
|
||||
|
||||
failed = failed ||
|
||||
self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
|
||||
|
||||
failed = failed ||
|
||||
self.clear_inputs.iter().any(|input| {
|
||||
pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value
|
||||
});
|
||||
!failed
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction's clear input
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct ClearInput {
|
||||
/// Input's value (amount)
|
||||
pub value: u64,
|
||||
/// Input's token ID
|
||||
pub token_id: DrkTokenId,
|
||||
/// Blinding factor for `value`
|
||||
pub value_blind: DrkValueBlind,
|
||||
/// Blinding factor for `token_id`
|
||||
pub token_blind: DrkValueBlind,
|
||||
/// Public key for the signature
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous input
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
/// Public inputs for the zero-knowledge proof
|
||||
pub revealed: BurnRevealedValues,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous output
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Output {
|
||||
/// Public inputs for the zero-knowledge proof
|
||||
pub revealed: MintRevealedValues,
|
||||
/// The encrypted note
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
VerifyFailed(#[from] VerifyFailed),
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
|
||||
/// Transaction verification errors
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum VerifyFailed {
|
||||
#[error("Transaction has no inputs")]
|
||||
LackingInputs,
|
||||
|
||||
#[error("Transaction has no outputs")]
|
||||
LackingOutputs,
|
||||
|
||||
#[error("Invalid cashier/faucet public key for clear input {0}")]
|
||||
InvalidCashierOrFaucetKey(usize),
|
||||
|
||||
#[error("Invalid Merkle root for input {0}")]
|
||||
InvalidMerkle(usize),
|
||||
|
||||
#[error("Spend hook invoking function is not attached")]
|
||||
SpendHookNotSatisfied,
|
||||
|
||||
#[error("Nullifier already exists for input {0}")]
|
||||
NullifierExists(usize),
|
||||
|
||||
#[error("Token commitments in inputs or outputs to not match")]
|
||||
TokenMismatch,
|
||||
|
||||
#[error("Money in does not match money out (value commitments)")]
|
||||
MissingFunds,
|
||||
|
||||
#[error("Failed verifying zk proofs: {0}")]
|
||||
ProofVerifyFailed(String),
|
||||
|
||||
#[error("Internal error: {0}")]
|
||||
InternalError(String),
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<Error> for VerifyFailed {
|
||||
fn from(err: Error) -> Self {
|
||||
Self::InternalError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for VerifyFailed {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
/// Result type used in transaction verifications
|
||||
pub type VerifyResult<T> = std::result::Result<T, VerifyFailed>;
|
||||
219
example/dao/src/contract/money/transfer/wallet.rs
Normal file
219
example/dao/src/contract/money/transfer/wallet.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use pasta_curves::group::ff::Field;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
burn_proof::create_burn_proof,
|
||||
keypair::{PublicKey, SecretKey},
|
||||
merkle_node::MerkleNode,
|
||||
mint_proof::create_mint_proof,
|
||||
types::{
|
||||
DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind,
|
||||
DrkValueBlind,
|
||||
},
|
||||
},
|
||||
Result,
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::money::{
|
||||
transfer::validate::{CallData, ClearInput, Input, Output},
|
||||
CONTRACT_ID,
|
||||
},
|
||||
note,
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub serial: DrkSerial,
|
||||
pub value: u64,
|
||||
pub token_id: DrkTokenId,
|
||||
pub spend_hook: DrkSpendHook,
|
||||
pub user_data: DrkUserData,
|
||||
pub coin_blind: DrkCoinBlind,
|
||||
pub value_blind: DrkValueBlind,
|
||||
pub token_blind: DrkValueBlind,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub clear_inputs: Vec<BuilderClearInputInfo>,
|
||||
pub inputs: Vec<BuilderInputInfo>,
|
||||
pub outputs: Vec<BuilderOutputInfo>,
|
||||
}
|
||||
|
||||
pub struct BuilderClearInputInfo {
|
||||
pub value: u64,
|
||||
pub token_id: DrkTokenId,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct BuilderInputInfo {
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub secret: SecretKey,
|
||||
pub note: Note,
|
||||
pub user_data_blind: DrkUserDataBlind,
|
||||
pub value_blind: DrkValueBlind,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct BuilderOutputInfo {
|
||||
pub value: u64,
|
||||
pub token_id: DrkTokenId,
|
||||
pub public: PublicKey,
|
||||
pub serial: DrkSerial,
|
||||
pub coin_blind: DrkCoinBlind,
|
||||
pub spend_hook: DrkSpendHook,
|
||||
pub user_data: DrkUserData,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn compute_remainder_blind(
|
||||
clear_inputs: &[ClearInput],
|
||||
input_blinds: &[DrkValueBlind],
|
||||
output_blinds: &[DrkValueBlind],
|
||||
) -> DrkValueBlind {
|
||||
let mut total = DrkValueBlind::zero();
|
||||
|
||||
for input in clear_inputs {
|
||||
total += input.value_blind;
|
||||
}
|
||||
|
||||
for input_blind in input_blinds {
|
||||
total += input_blind;
|
||||
}
|
||||
|
||||
for output_blind in output_blinds {
|
||||
total -= output_blind;
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> Result<FuncCall> {
|
||||
assert!(self.clear_inputs.len() + self.inputs.len() > 0);
|
||||
|
||||
let mut clear_inputs = vec![];
|
||||
let token_blind = DrkValueBlind::random(&mut OsRng);
|
||||
for input in &self.clear_inputs {
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
let value_blind = DrkValueBlind::random(&mut OsRng);
|
||||
|
||||
let clear_input = ClearInput {
|
||||
value: input.value,
|
||||
token_id: input.token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
signature_public,
|
||||
};
|
||||
clear_inputs.push(clear_input);
|
||||
}
|
||||
|
||||
let mut proofs = vec![];
|
||||
let mut inputs = vec![];
|
||||
let mut input_blinds = vec![];
|
||||
|
||||
for input in self.inputs {
|
||||
let value_blind = input.value_blind;
|
||||
input_blinds.push(value_blind);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Native(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not native info")
|
||||
};
|
||||
let burn_pk = &zk_info.proving_key;
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note.clone();
|
||||
|
||||
let (burn_proof, revealed) = create_burn_proof(
|
||||
burn_pk,
|
||||
note.value,
|
||||
note.token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
note.serial,
|
||||
note.spend_hook,
|
||||
note.user_data,
|
||||
input.user_data_blind,
|
||||
note.coin_blind,
|
||||
input.secret,
|
||||
input.leaf_position,
|
||||
input.merkle_path.clone(),
|
||||
input.signature_secret,
|
||||
)?;
|
||||
proofs.push(burn_proof);
|
||||
|
||||
let input = Input { revealed };
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let mut outputs = vec![];
|
||||
let mut output_blinds = vec![];
|
||||
// This value_blind calc assumes there will always be at least a single output
|
||||
assert!(self.outputs.len() > 0);
|
||||
|
||||
for (i, output) in self.outputs.iter().enumerate() {
|
||||
let value_blind = if i == self.outputs.len() - 1 {
|
||||
Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds)
|
||||
} else {
|
||||
DrkValueBlind::random(&mut OsRng)
|
||||
};
|
||||
output_blinds.push(value_blind);
|
||||
|
||||
let serial = output.serial;
|
||||
let coin_blind = output.coin_blind;
|
||||
|
||||
let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Native(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not native info")
|
||||
};
|
||||
let mint_pk = &zk_info.proving_key;
|
||||
|
||||
let (mint_proof, revealed) = create_mint_proof(
|
||||
mint_pk,
|
||||
output.value,
|
||||
output.token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
serial,
|
||||
output.spend_hook,
|
||||
output.user_data,
|
||||
coin_blind,
|
||||
output.public,
|
||||
)?;
|
||||
proofs.push(mint_proof);
|
||||
|
||||
let note = Note {
|
||||
serial,
|
||||
value: output.value,
|
||||
token_id: output.token_id,
|
||||
spend_hook: output.spend_hook,
|
||||
user_data: output.user_data,
|
||||
coin_blind,
|
||||
value_blind,
|
||||
token_blind,
|
||||
};
|
||||
|
||||
let encrypted_note = note::encrypt(¬e, &output.public)?;
|
||||
|
||||
let output = Output { revealed, enc_note: encrypted_note };
|
||||
outputs.push(output);
|
||||
}
|
||||
|
||||
let call_data = CallData { clear_inputs, inputs, outputs };
|
||||
|
||||
Ok(FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
})
|
||||
}
|
||||
}
|
||||
1139
example/dao/src/dao.rs
Normal file
1139
example/dao/src/dao.rs
Normal file
File diff suppressed because it is too large
Load Diff
56
example/dao/src/error.rs
Normal file
56
example/dao/src/error.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DaoError {
|
||||
#[error("No Proposals found")]
|
||||
NoProposals,
|
||||
#[error("No DAO params found")]
|
||||
DaoNotConfigured,
|
||||
#[error("State transition failed: '{0}'")]
|
||||
StateTransitionFailed(String),
|
||||
#[error("Wallet does not exist")]
|
||||
NoWalletFound,
|
||||
#[error("State not found")]
|
||||
StateNotFound,
|
||||
#[error("InternalError")]
|
||||
Darkfi(#[from] darkfi::error::Error),
|
||||
#[error("Verify proof failed: '{0}', '{0}'")]
|
||||
VerifyProofFailed(usize, String),
|
||||
}
|
||||
|
||||
pub type DaoResult<T> = std::result::Result<T, DaoError>;
|
||||
|
||||
pub enum RpcError {
|
||||
Vote = -32101,
|
||||
Propose = -32102,
|
||||
Exec = -32103,
|
||||
Airdrop = -32104,
|
||||
Mint = -32105,
|
||||
Keygen = -32106,
|
||||
Create = -32107,
|
||||
Parse = -32108,
|
||||
Balance = -32109,
|
||||
}
|
||||
|
||||
fn to_tuple(e: RpcError) -> (i64, String) {
|
||||
let msg = match e {
|
||||
RpcError::Vote => "Failed to cast a Vote",
|
||||
RpcError::Propose => "Failed to generate a Proposal",
|
||||
RpcError::Airdrop => "Failed to transfer an airdrop",
|
||||
RpcError::Keygen => "Failed to generate keypair",
|
||||
RpcError::Create => "Failed to create DAO",
|
||||
RpcError::Exec => "Failed to execute Proposal",
|
||||
RpcError::Mint => "Failed to mint DAO treasury",
|
||||
RpcError::Parse => "Generic parsing error",
|
||||
RpcError::Balance => "Failed to get balance",
|
||||
};
|
||||
|
||||
(e as i64, msg.to_string())
|
||||
}
|
||||
|
||||
pub fn server_error(e: RpcError, id: Value) -> JsonResult {
|
||||
let (code, msg) = to_tuple(e);
|
||||
JsonError::new(ServerError(code), Some(msg), id).into()
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use crypto_api_chachapoly::ChachaPolyIetf;
|
||||
use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
@@ -9,6 +8,7 @@ use darkfi::{
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
pub const AEAD_TAG_SIZE: usize = 16;
|
||||
|
||||
@@ -51,8 +51,7 @@ impl EncryptedNote2 {
|
||||
self.ciphertext.len() - AEAD_TAG_SIZE
|
||||
);
|
||||
|
||||
let t = T::decode(&plaintext[..])?;
|
||||
Ok(t)
|
||||
T::decode(&plaintext[..]).map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
265
example/dao/src/util.rs
Normal file
265
example/dao/src/util.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
use std::{any::Any, collections::HashMap, hash::Hasher};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use log::debug;
|
||||
use pasta_curves::{
|
||||
group::ff::{Field, PrimeField},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
keypair::{PublicKey, SecretKey},
|
||||
proof::{ProvingKey, VerifyingKey},
|
||||
schnorr::{SchnorrPublic, SchnorrSecret, Signature},
|
||||
types::DrkCircuitField,
|
||||
Proof,
|
||||
},
|
||||
zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
|
||||
zkas::decoder::ZkBinary,
|
||||
Error,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
|
||||
use crate::error::{DaoError, DaoResult};
|
||||
|
||||
/// Parse pallas::Base from a base58-encoded string
|
||||
pub fn parse_b58(s: &str) -> std::result::Result<pallas::Base, darkfi::Error> {
|
||||
let bytes = bs58::decode(s).into_vec()?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string"))
|
||||
}
|
||||
|
||||
let ret = pallas::Base::from_repr(bytes.try_into().unwrap());
|
||||
if ret.is_some().unwrap_u8() == 1 {
|
||||
return Ok(ret.unwrap())
|
||||
}
|
||||
|
||||
Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string"))
|
||||
}
|
||||
|
||||
// The token of the DAO treasury.
|
||||
lazy_static! {
|
||||
pub static ref DRK_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
|
||||
// Governance tokens that are airdropped to users to operate the DAO.
|
||||
lazy_static! {
|
||||
pub static ref GOV_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct HashableBase(pub pallas::Base);
|
||||
|
||||
impl std::hash::Hash for HashableBase {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let bytes = self.0.to_repr();
|
||||
bytes.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkBinaryContractInfo {
|
||||
pub k_param: u32,
|
||||
pub bincode: ZkBinary,
|
||||
pub proving_key: ProvingKey,
|
||||
pub verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkNativeContractInfo {
|
||||
pub proving_key: ProvingKey,
|
||||
pub verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ZkContractInfo {
|
||||
Binary(ZkBinaryContractInfo),
|
||||
Native(ZkNativeContractInfo),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkContractTable {
|
||||
// Key will be a hash of zk binary contract on chain
|
||||
table: HashMap<String, ZkContractInfo>,
|
||||
}
|
||||
|
||||
impl ZkContractTable {
|
||||
pub fn new() -> Self {
|
||||
Self { table: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) {
|
||||
let witnesses = empty_witnesses(&bincode);
|
||||
let circuit = ZkCircuit::new(witnesses, bincode.clone());
|
||||
let proving_key = ProvingKey::build(k_param, &circuit);
|
||||
let verifying_key = VerifyingKey::build(k_param, &circuit);
|
||||
let info = ZkContractInfo::Binary(ZkBinaryContractInfo {
|
||||
k_param,
|
||||
bincode,
|
||||
proving_key,
|
||||
verifying_key,
|
||||
});
|
||||
self.table.insert(key, info);
|
||||
}
|
||||
|
||||
pub fn add_native(
|
||||
&mut self,
|
||||
key: String,
|
||||
proving_key: ProvingKey,
|
||||
verifying_key: VerifyingKey,
|
||||
) {
|
||||
self.table.insert(
|
||||
key,
|
||||
ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> {
|
||||
self.table.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Transaction {
|
||||
pub func_calls: Vec<FuncCall>,
|
||||
pub signatures: Vec<Signature>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Verify ZK contracts for the entire tx
|
||||
/// In real code, we could parallelize this for loop
|
||||
/// TODO: fix use of unwrap with Result type stuff
|
||||
pub fn zk_verify(&self, zk_bins: &ZkContractTable) -> DaoResult<()> {
|
||||
for func_call in &self.func_calls {
|
||||
let proofs_public_vals = &func_call.call_data.zk_public_values();
|
||||
|
||||
assert_eq!(
|
||||
proofs_public_vals.len(),
|
||||
func_call.proofs.len(),
|
||||
"proof_public_vals.len()={} and func_call.proofs.len()={} do not match",
|
||||
proofs_public_vals.len(),
|
||||
func_call.proofs.len()
|
||||
);
|
||||
for (i, (proof, (key, public_vals))) in
|
||||
func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate()
|
||||
{
|
||||
match zk_bins.lookup(key).unwrap() {
|
||||
ZkContractInfo::Binary(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(&verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(DaoError::VerifyProofFailed(i, key.to_string()))
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
ZkContractInfo::Native(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(&verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(DaoError::VerifyProofFailed(i, key.to_string()))
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
};
|
||||
debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_sigs(&self) {
|
||||
let mut unsigned_tx_data = vec![];
|
||||
for (i, (func_call, signature)) in
|
||||
self.func_calls.iter().zip(self.signatures.clone()).enumerate()
|
||||
{
|
||||
func_call.encode(&mut unsigned_tx_data).expect("failed to encode data");
|
||||
let signature_pub_keys = func_call.call_data.signature_public_keys();
|
||||
for signature_pub_key in signature_pub_keys {
|
||||
let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature);
|
||||
assert!(verify_result, "verify sigs[{}] failed", i);
|
||||
}
|
||||
debug!(target: "demo", "verify_sigs({}) passed", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(signature_secrets: Vec<SecretKey>, func_calls: &Vec<FuncCall>) -> Vec<Signature> {
|
||||
let mut signatures = vec![];
|
||||
let mut unsigned_tx_data = vec![];
|
||||
for (_i, (signature_secret, func_call)) in
|
||||
signature_secrets.iter().zip(func_calls.iter()).enumerate()
|
||||
{
|
||||
func_call.encode(&mut unsigned_tx_data).expect("failed to encode data");
|
||||
let signature = signature_secret.sign(&unsigned_tx_data[..]);
|
||||
signatures.push(signature);
|
||||
}
|
||||
signatures
|
||||
}
|
||||
|
||||
type ContractId = pallas::Base;
|
||||
type FuncId = pallas::Base;
|
||||
|
||||
pub struct FuncCall {
|
||||
pub contract_id: ContractId,
|
||||
pub func_id: FuncId,
|
||||
pub call_data: Box<dyn CallDataBase + Send + Sync>,
|
||||
pub proofs: Vec<Proof>,
|
||||
}
|
||||
|
||||
impl Encodable for FuncCall {
|
||||
fn encode<W: std::io::Write>(&self, mut w: W) -> std::result::Result<usize, std::io::Error> {
|
||||
let mut len = 0;
|
||||
len += self.contract_id.encode(&mut w)?;
|
||||
len += self.func_id.encode(&mut w)?;
|
||||
len += self.proofs.encode(&mut w)?;
|
||||
len += self.call_data.encode_bytes(&mut w)?;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CallDataBase {
|
||||
// Public values for verifying the proofs
|
||||
// Needed so we can convert internal types so they can be used in Proof::verify()
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)>;
|
||||
|
||||
// For upcasting to CallData itself so it can be read in state_transition()
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
// Public keys we will use to verify transaction signatures.
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey>;
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error>;
|
||||
}
|
||||
|
||||
type GenericContractState = Box<dyn Any + Send>;
|
||||
|
||||
pub struct StateRegistry {
|
||||
pub states: HashMap<HashableBase, GenericContractState>,
|
||||
}
|
||||
|
||||
impl StateRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self { states: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) {
|
||||
debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id);
|
||||
self.states.insert(HashableBase(contract_id), state);
|
||||
}
|
||||
|
||||
pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> {
|
||||
self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut())
|
||||
}
|
||||
|
||||
pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> {
|
||||
self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpdateBase {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry);
|
||||
}
|
||||
Reference in New Issue
Block a user