From 0ba4ad95febd006ccca9c8cea58f476b0a499513 Mon Sep 17 00:00:00 2001 From: narodnik Date: Fri, 6 May 2022 20:48:44 +0200 Subject: [PATCH] daod: tx verification --- bin/daod/demo/main.py | 189 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 8 deletions(-) diff --git a/bin/daod/demo/main.py b/bin/daod/demo/main.py index a90c84934..21b20ac7c 100644 --- a/bin/daod/demo/main.py +++ b/bin/daod/demo/main.py @@ -1,3 +1,4 @@ +import hashlib import sys from collections import namedtuple from classnamespace import ClassNamespace @@ -38,15 +39,16 @@ class TransactionBuilder: return total % self.ec.order def build(self): - tx = Transaction() + tx = Transaction(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 = input.token_blind + 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) @@ -72,24 +74,40 @@ class TransactionBuilder: note.token_id = output.token_id note.coin_blind = self.ec.random_base() note.value_blind = value_blind + note.token_blind = token_blind tx_output = ClassNamespace() + tx_output.__name__ = "TransactionOutput" tx_output.mint_proof = MintProof( note.value, note.token_id, note.value_blind, - token_blind, tx_output.serial, coin_blind, public) + note.token_blind, note.serial, note.coin_blind, + output.public, 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, info) in zip(tx.inputs, self.inputs): + secret = info.signature_secret + signature = sign(unsigned_tx_data, secret, self.ec) + input.signature = signature + return tx class MintProof: def __init__(self, value, token_id, value_blind, token_blind, serial, - coin_blind, public): + coin_blind, public, ec): self.value = value self.token_id = token_id self.value_blind = value_blind @@ -98,26 +116,181 @@ class MintProof: self.coin_blind = coin_blind self.public = public + 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 + ) + + 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): - pass + 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 + ]) + +def pedersen_encrypt(x, y, ec): + vcv = ec.multiply(x, ec.G) + vcr = ec.multiply(y, ec.H) + return ec.add(vcv, vcr) + +def ff_hash(p, *args): + hasher = hashlib.sha256() + 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 _: + raise Exception(f"unknown hash arg '{arg}' type: {type(arg)}") + 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 class Transaction: - def __init__(self): + def __init__(self, ec): self.clear_inputs = [] self.inputs = [] self.outputs = [] + self.ec = ec + + def partial_encode(self): + 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 + def main(argv): ec = pallas_curve() + + secret = ec.random_scalar() + public = ec.multiply(secret, ec.G) + + initial_supply = 21000 + token_id = 110 + + signature_secret = ec.random_scalar() + builder = TransactionBuilder(ec) - builder.add_output(44, 110, "1234") + builder.add_clear_input(initial_supply, token_id, signature_secret) + builder.add_output(initial_supply, token_id, public) tx = builder.build() + is_verify, reason = tx.verify() + if not is_verify: + print(f"tx verify failed: {reason}", file=sys.stderr) + return -1 + + return 0 + if __name__ == "__main__": sys.exit(main(sys.argv))