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