Files
darkfi/bin/daod/demo/money.py
2022-08-15 10:19:44 +02:00

332 lines
11 KiB
Python

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,
])