pset: add value and asset proofs

This commit is contained in:
Stepan Snigirev 2021-10-06 14:46:00 +02:00
parent 567903b018
commit 75230360b6
3 changed files with 121 additions and 16 deletions

View File

@ -124,6 +124,8 @@ class LOutputScope(OutputScope):
self.blinding_pubkey = None
self.asset = None
self.blinder_index = None
self.value_proof = None
self.asset_proof = None
if vout:
self.asset = vout.asset
self._verified = False
@ -140,18 +142,39 @@ class LOutputScope(OutputScope):
e = PSBTError("Invalid commitments")
if self.asset and self.asset_commitment:
# we can't verify asset
if not self.asset_blinding_factor:
raise e
gen = secp256k1.generator_generate_blinded(self.asset, self.asset_blinding_factor)
if self.asset_commitment != secp256k1.generator_serialize(gen):
if not self.asset_blinding_factor and not self.asset_proof:
raise e
gen = secp256k1.generator_parse(self.asset_commitment)
# we have blinding factor
if self.asset_blinding_factor:
if gen != secp256k1.generator_generate_blinded(self.asset, self.asset_blinding_factor):
raise e
# otherwise use asset proof
else:
surj_proof = secp256k1.surjectionproof_parse(self.asset_proof)
gen_asset = secp256k1.generator_generate(self.asset)
if not secp256k1.surjectionproof_verify(surj_proof, [gen_asset], gen):
raise e
if self.value and self.value_commitment:
if gen is None:
raise e
value_commitment = secp256k1.pedersen_commit(self.value_blinding_factor, self.value, gen)
if self.value_commitment != secp256k1.pedersen_commitment_serialize(value_commitment):
if not gen or not (self.value_blinding_factor or self.value_proof):
raise e
# we have blinding factor
if self.value_blinding_factor:
value_commitment = secp256k1.pedersen_commit(self.value_blinding_factor, self.value, gen)
if self.value_commitment != secp256k1.pedersen_commitment_serialize(value_commitment):
raise e
# otherwise use value proof
else:
value_commitment = secp256k1.pedersen_commitment_parse(self.value_commitment)
min_value, max_value = secp256k1.rangeproof_verify(
self.value_proof,
value_commitment,
b"",
gen,
)
if (min_value != max_value) or (self.value != min_value):
raise e
self._verified = True
return self._verified
@ -162,6 +185,8 @@ class LOutputScope(OutputScope):
self.surjection_proof = None
self.value_blinding_factor = None
self.asset_blinding_factor = None
self.asset_proof = None
self.value_proof = None
if self.value_commitment:
self.value = None
if self.asset_commitment:
@ -244,6 +269,10 @@ class LOutputScope(OutputScope):
self.ecdh_pubkey = v
elif k == b"\xfc\x04pset\x08":
self.blinder_index = int.from_bytes(v, 'little')
elif k == b'\xfc\x04pset\x09':
self.value_proof = v
elif k == b'\xfc\x04pset\x0a':
self.asset_proof = v
else:
self.unknown[k] = v
@ -305,6 +334,12 @@ class LOutputScope(OutputScope):
if self.blinder_index is not None:
r += ser_string(stream, b"\xfc\x04pset\x08")
r += ser_string(stream, self.blinder_index.to_bytes(4, 'little'))
if self.value_proof is not None:
r += ser_string(stream, b"\xfc\x04pset\x09")
r += ser_string(stream, self.value_proof)
if self.asset_proof is not None:
r += ser_string(stream, b"\xfc\x04pset\x0a")
r += ser_string(stream, self.asset_proof)
# separator
if not skip_separator:
r += stream.write(b"\x00")
@ -390,6 +425,24 @@ class PSET(PSBT):
rangeproof_nonce = hashes.tagged_hash("liquid/range_proof", txseed+i.to_bytes(4,'little'))
out.reblind(rangeproof_nonce)
# generate asset proof
gen_asset = secp256k1.generator_generate(out.asset)
proof, idx = secp256k1.surjectionproof_initialize([out.asset], out.asset, b"\x00"*32, 1, 1)
proof = secp256k1.surjectionproof_generate(proof, idx, [gen_asset], gen, b"\x00"*32, out.asset_blinding_factor)
out.asset_proof = secp256k1.surjectionproof_serialize(proof)
# generate value proof
value_proof_nonce = hashes.tagged_hash("liquid/value_proof", txseed+i.to_bytes(4,'little'))
out.value_proof = secp256k1.rangeproof_sign(
value_proof_nonce, out.value, value_commitment,
out.value_blinding_factor, b"",
b"", gen,
out.value, # min_value
-1, # exp
0, # min bits
)
def fee(self):
fee = 0
for out in self.tx.vout:

View File

@ -297,13 +297,37 @@ def _init(flags=(CONTEXT_SIGN | CONTEXT_VERIFY)):
secp256k1.secp256k1_pedersen_verify_tally.restype = c_int
# rangeproof
secp256k1.secp256k1_rangeproof_rewind.argtypes = [c_void_p, c_char_p, POINTER(c_uint64), c_char_p, POINTER(c_uint64),
c_char_p, POINTER(c_uint64), POINTER(c_uint64),
c_char_p, c_char_p, c_uint64,
c_char_p, c_uint64,
c_char_p]
secp256k1.secp256k1_rangeproof_rewind.argtypes = [
c_void_p, # ctx
c_char_p, # vbf out
POINTER(c_uint64), # value out
c_char_p, # message out
POINTER(c_uint64), # msg out len
c_char_p, # nonce
POINTER(c_uint64), # min value
POINTER(c_uint64), # max value
c_char_p, # pedersen commitment
c_char_p, # range proof
c_uint64, # proof len
c_char_p, # extra commitment (scriptpubkey)
c_uint64, # extra len
c_char_p, # generator
]
secp256k1.secp256k1_rangeproof_rewind.restype = c_int
secp256k1.secp256k1_rangeproof_verify.argtypes = [
c_void_p, # ctx
POINTER(c_uint64), # min value
POINTER(c_uint64), # max value
c_char_p, # pedersen commitment
c_char_p, # proof
c_uint64, # proof len
c_char_p, # extra
c_uint64, # extra len
c_char_p, # generator
]
secp256k1.secp256k1_rangeproof_verify.restype = c_int
secp256k1.secp256k1_rangeproof_sign.argtypes = [
c_void_p, # ctx
c_char_p, # proof
@ -836,8 +860,9 @@ def rangeproof_rewind(proof, nonce, value_commitment, script_pubkey, generator,
if len(value_commitment)!=64:
raise ValueError("Value commitment should be 64 bytes long")
msg = b"\x00"*message_length
pointer = POINTER(c_uint64)
msg = b"\x00"*message_length
msglen = pointer(c_uint64(len(msg)))
vbf_out = b"\x00"*32
@ -854,6 +879,31 @@ def rangeproof_rewind(proof, nonce, value_commitment, script_pubkey, generator,
raise RuntimeError("Failed to rewind the proof")
return value_out.contents.value, vbf_out, msg[:msglen.contents.value], min_value.contents.value, max_value.contents.value
# rangeproof
@locked
def rangeproof_verify(proof, value_commitment, script_pubkey, generator, context=_secp.ctx):
if len(generator)!=64:
raise ValueError("Generator should be 64 bytes long")
if len(value_commitment)!=64:
raise ValueError("Value commitment should be 64 bytes long")
pointer = POINTER(c_uint64)
min_value = pointer(c_uint64(0))
max_value = pointer(c_uint64(0))
res = _secp.secp256k1_rangeproof_verify(
context,
min_value,
max_value,
value_commitment,
proof, len(proof),
script_pubkey, len(script_pubkey),
generator,
)
if res != 1:
raise RuntimeError("Failed to verify the proof")
return min_value.contents.value, max_value.contents.value
@locked
def rangeproof_sign(nonce, value, value_commitment, vbf, message, extra, gen, min_value=1, exp=0, min_bits=52, context=_secp.ctx):
if value == 0:

File diff suppressed because one or more lines are too long