forked from trezor/trezor-firmware
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
elements: add transaction blinding/unblinding API
Re-generated messages using: $ (cd ../tools; ./build_protobuf) $ (cd python; pip3 install --user -e .) Supports balancing Pedersen commitments, rangeproofs and surjection proofs. TODOs: * Randomness is passed as arguments.
- Loading branch information
Showing
29 changed files
with
1,782 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
syntax = "proto2"; | ||
package hw.trezor.messages.liquid; | ||
|
||
// Sugar for easier handling in Java | ||
option java_package = "com.satoshilabs.trezor.lib.protobuf"; | ||
option java_outer_classname = "TrezorMessageLiquid"; | ||
|
||
message LiquidAmount { | ||
optional uint64 value = 1; | ||
optional bytes value_blind = 2; // 32-bytes or None (-> should be derived on-device) | ||
optional bytes asset = 3; // 32-bytes | ||
optional bytes asset_blind = 4; // 32-bytes or None (-> should be derived on-device) | ||
} | ||
|
||
/** | ||
* Request: Blind specified output | ||
* @start | ||
* @next LiquidBlindedOutput | ||
*/ | ||
message LiquidBlindOutput { | ||
optional LiquidAmount amount = 1; | ||
optional bytes ecdh_pubkey = 2; // recipient ECDH pubkey, for ECDH nonce generation | ||
optional bytes ecdh_privkey = 3; // our ECDH private key, 32-bytes or None (-> should be derived on-device) | ||
optional bytes script_pubkey = 4; | ||
|
||
// Used for surjection proof generation: | ||
optional bytes random_seed32 = 5; // for input asset index selection | ||
} | ||
|
||
/** | ||
* Response: blinded output | ||
* @end | ||
*/ | ||
message LiquidBlindedOutput { | ||
optional bytes conf_value = 1; | ||
optional bytes conf_asset = 2; | ||
optional bytes ecdh_pubkey = 3; | ||
optional bytes script_pubkey = 4; // same from LiquidBlindOutput | ||
optional bytes range_proof = 5; | ||
optional bytes surjection_proof = 6; | ||
} | ||
|
||
|
||
message LiquidUnblindOutput { | ||
optional LiquidBlindedOutput blinded = 1; | ||
optional bytes ecdh_privkey = 2; // -> should be derived on-device | ||
} | ||
|
||
message LiquidBalanceBlinds { | ||
repeated LiquidAmount inputs = 1; // only `value`, `value_blind` and `asset_blind` are used | ||
repeated LiquidAmount outputs = 2; // only `value`, `value_blind` and `asset_blind` are used | ||
} | ||
|
||
message LiquidBlindTx { | ||
repeated LiquidAmount inputs = 1; | ||
repeated LiquidBlindOutput outputs = 2; | ||
} | ||
|
||
message LiquidBlindTxRequest { | ||
optional uint32 output_index = 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from trezor import wire | ||
from trezor.messages import MessageType | ||
|
||
|
||
def boot(): | ||
ns = [["slip21"]] | ||
wire.add(MessageType.LiquidBlindTx, __name__, "blind_tx", ns) | ||
wire.add(MessageType.LiquidUnblindOutput, __name__, "unblind_output", ns) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from trezor.crypto.curve import secp256k1_zkp | ||
from trezor.crypto.hashlib import sha256 | ||
from trezor.messages.LiquidAmount import LiquidAmount | ||
from trezor.messages.LiquidBlindedOutput import LiquidBlindedOutput | ||
|
||
BLIND_SIZE = 32 # in bytes | ||
|
||
|
||
def balance_blinds(context, inputs, outputs): | ||
amounts = inputs + outputs | ||
num_of_inputs = len(inputs) | ||
for a in amounts: | ||
if len(a.asset_blind) != BLIND_SIZE: | ||
raise ValueError("incorrect asset_blind length") | ||
if len(a.value_blind) != BLIND_SIZE: | ||
raise ValueError("incorrect value_blind length") | ||
|
||
values = tuple(a.value for a in amounts) | ||
assets = tuple(a.asset for a in amounts) | ||
value_blinds = b"".join(bytes(a.value_blind) for a in amounts) | ||
asset_blinds = b"".join(bytes(a.asset_blind) for a in amounts) | ||
|
||
value_blinds = bytearray(value_blinds) # to be updated | ||
context.balance_blinds(values, value_blinds, asset_blinds, num_of_inputs) | ||
value_blinds = bytes(value_blinds) | ||
|
||
assert len(value_blinds) == BLIND_SIZE * len(amounts) | ||
assert len(asset_blinds) == BLIND_SIZE * len(amounts) | ||
|
||
balanced = [ | ||
LiquidAmount( | ||
value=values[i], | ||
value_blind=value_blinds[i * BLIND_SIZE : (i + 1) * BLIND_SIZE], | ||
asset=assets[i], | ||
asset_blind=asset_blinds[i * BLIND_SIZE : (i + 1) * BLIND_SIZE], | ||
) | ||
for i in range(len(amounts)) | ||
] | ||
balanced_inputs = balanced[:num_of_inputs] | ||
balanced_outputs = balanced[num_of_inputs:] | ||
|
||
assert balanced_inputs == inputs # inputs should not change | ||
assert len(balanced_outputs) == len(outputs) | ||
for output, balanced_output in zip(outputs, balanced_outputs): | ||
output.value_blind = balanced_output.value_blind | ||
assert balanced_outputs == outputs # only value blinders may change | ||
|
||
|
||
def ecdh(context, our_privkey, peer_pubkey): | ||
compressed = True | ||
shared_pubkey = context.multiply(our_privkey, peer_pubkey, compressed) | ||
return sha256(sha256(shared_pubkey).digest()).digest() | ||
|
||
|
||
def blind_output(context, output, inputs, proof_buffer): | ||
peer_pubkey = output.ecdh_pubkey | ||
our_privkey = output.ecdh_privkey # TODO: derive via BIP-32 | ||
our_pubkey = context.publickey(our_privkey) | ||
nonce = ecdh(context, our_privkey, peer_pubkey) | ||
|
||
conf_asset = context.blind_generator(output.amount.asset, output.amount.asset_blind) | ||
# TODO: derive value_blind via HMAC with BIP-32 derived private key | ||
conf_value = context.pedersen_commit( | ||
output.amount.value, output.amount.value_blind, conf_asset | ||
) | ||
yield LiquidBlindedOutput( | ||
conf_value=conf_value, | ||
conf_asset=conf_asset, | ||
ecdh_pubkey=our_pubkey, | ||
script_pubkey=output.script_pubkey, | ||
) | ||
del peer_pubkey, our_privkey, our_pubkey | ||
|
||
asset_message = output.amount.asset + output.amount.asset_blind | ||
range_proof_view = context.rangeproof_sign( | ||
secp256k1_zkp.RangeProofConfig(min_value=1, exponent=0, bits=36), | ||
output.amount.value, | ||
conf_value, | ||
output.amount.value_blind, | ||
nonce, | ||
asset_message, | ||
output.script_pubkey, | ||
conf_asset, | ||
proof_buffer, | ||
) | ||
del asset_message, nonce, conf_asset, conf_value | ||
yield LiquidBlindedOutput(range_proof=range_proof_view) | ||
|
||
input_assets = b"".join(bytes(i.asset) for i in inputs) | ||
input_assets_blinds = b"".join(bytes(i.asset_blind) for i in inputs) | ||
surjection_proof = context.surjection_proof( | ||
output.amount.asset, | ||
output.amount.asset_blind, | ||
input_assets, | ||
input_assets_blinds, | ||
len(inputs), | ||
output.random_seed32, | ||
proof_buffer, | ||
) | ||
del input_assets, input_assets_blinds | ||
yield LiquidBlindedOutput(surjection_proof=surjection_proof) | ||
|
||
|
||
def unblind_output(context, blinded, ecdh_privkey, message_buffer): | ||
peer_pubkey = blinded.ecdh_pubkey | ||
our_privkey = ecdh_privkey # TODO: derive via BIP-32 | ||
nonce = ecdh(context, our_privkey, peer_pubkey) | ||
|
||
(value, value_blind) = context.rangeproof_rewind( | ||
blinded.conf_value, | ||
blinded.conf_asset, | ||
nonce, | ||
blinded.range_proof, | ||
blinded.script_pubkey, | ||
message_buffer, | ||
) | ||
|
||
return LiquidAmount( | ||
value=value, | ||
value_blind=value_blind, | ||
asset=bytes(message_buffer[0:32]), | ||
asset_blind=bytes(message_buffer[32:64]), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import gc | ||
|
||
from trezor.crypto.curve import secp256k1_zkp | ||
from trezor.messages.LiquidBlindedOutput import LiquidBlindedOutput | ||
from trezor.messages.LiquidBlindTx import LiquidBlindTx | ||
from trezor.messages.LiquidBlindTxRequest import LiquidBlindTxRequest | ||
|
||
from . import blind | ||
|
||
|
||
async def blind_tx(ctx, msg, keychain): | ||
gc.collect() | ||
with secp256k1_zkp.Context() as context: | ||
proof_buffer = secp256k1_zkp.allocate_proof_buffer() | ||
# Allow the device to allocate memory before the parsing the actual message | ||
dummy_ack = LiquidBlindedOutput() | ||
msg = await ctx.call(dummy_ack, LiquidBlindTx) | ||
blind.balance_blinds( | ||
context=context, | ||
inputs=msg.inputs, | ||
outputs=[out.amount for out in msg.outputs], | ||
) | ||
req = await ctx.call(dummy_ack, LiquidBlindTxRequest) | ||
while req.output_index is not None: | ||
blinded_iter = blind.blind_output( | ||
context=context, | ||
output=msg.outputs[req.output_index], | ||
inputs=msg.inputs, | ||
proof_buffer=proof_buffer, | ||
) | ||
for blinded in blinded_iter: | ||
dummy = await ctx.call(blinded, LiquidBlindTxRequest) | ||
assert dummy.output_index == req.output_index | ||
del blinded # MUST be discarded before next iteration | ||
|
||
sentinel = LiquidBlindedOutput() # sentinel value | ||
req = await ctx.call(sentinel, LiquidBlindTxRequest) # next output | ||
del proof_buffer | ||
return dummy_ack |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import gc | ||
|
||
from trezor.crypto.curve import secp256k1_zkp | ||
from trezor.messages.LiquidAmount import LiquidAmount | ||
from trezor.messages.LiquidUnblindOutput import LiquidUnblindOutput | ||
|
||
from . import blind | ||
|
||
from apps.common.seed import Slip77 | ||
|
||
|
||
async def unblind_output(ctx, msg, keychain): | ||
gc.collect() | ||
# NOTE: we can reuse {range,surjection}-proof buffer for message recovery | ||
message_buffer = secp256k1_zkp.allocate_proof_buffer() | ||
with secp256k1_zkp.Context() as context: | ||
# Allow the device to allocate memory before the parsing the actual message | ||
msg = await ctx.call(LiquidAmount(), LiquidUnblindOutput) | ||
ecdh_privkey = msg.ecdh_privkey or Slip77(keychain).derive_blinding_private_key( | ||
script=msg.blinded.script_pubkey | ||
) | ||
|
||
return blind.unblind_output( | ||
context=context, | ||
blinded=msg.blinded, | ||
ecdh_privkey=ecdh_privkey, | ||
message_buffer=message_buffer, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Automatically generated by pb2py | ||
# fmt: off | ||
import protobuf as p | ||
|
||
if __debug__: | ||
try: | ||
from typing import Dict, List, Optional | ||
from typing_extensions import Literal # noqa: F401 | ||
except ImportError: | ||
Dict, List, Optional = None, None, None # type: ignore | ||
|
||
|
||
class LiquidAmount(p.MessageType): | ||
MESSAGE_WIRE_TYPE = 805 | ||
|
||
def __init__( | ||
self, | ||
value: int = None, | ||
value_blind: bytes = None, | ||
asset: bytes = None, | ||
asset_blind: bytes = None, | ||
) -> None: | ||
self.value = value | ||
self.value_blind = value_blind | ||
self.asset = asset | ||
self.asset_blind = asset_blind | ||
|
||
@classmethod | ||
def get_fields(cls) -> Dict: | ||
return { | ||
1: ('value', p.UVarintType, 0), | ||
2: ('value_blind', p.BytesType, 0), | ||
3: ('asset', p.BytesType, 0), | ||
4: ('asset_blind', p.BytesType, 0), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Automatically generated by pb2py | ||
# fmt: off | ||
import protobuf as p | ||
|
||
from .LiquidAmount import LiquidAmount | ||
|
||
if __debug__: | ||
try: | ||
from typing import Dict, List, Optional | ||
from typing_extensions import Literal # noqa: F401 | ||
except ImportError: | ||
Dict, List, Optional = None, None, None # type: ignore | ||
|
||
|
||
class LiquidBalanceBlinds(p.MessageType): | ||
|
||
def __init__( | ||
self, | ||
inputs: List[LiquidAmount] = None, | ||
outputs: List[LiquidAmount] = None, | ||
) -> None: | ||
self.inputs = inputs if inputs is not None else [] | ||
self.outputs = outputs if outputs is not None else [] | ||
|
||
@classmethod | ||
def get_fields(cls) -> Dict: | ||
return { | ||
1: ('inputs', LiquidAmount, p.FLAG_REPEATED), | ||
2: ('outputs', LiquidAmount, p.FLAG_REPEATED), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Automatically generated by pb2py | ||
# fmt: off | ||
import protobuf as p | ||
|
||
from .LiquidAmount import LiquidAmount | ||
|
||
if __debug__: | ||
try: | ||
from typing import Dict, List, Optional | ||
from typing_extensions import Literal # noqa: F401 | ||
except ImportError: | ||
Dict, List, Optional = None, None, None # type: ignore | ||
|
||
|
||
class LiquidBlindOutput(p.MessageType): | ||
|
||
def __init__( | ||
self, | ||
amount: LiquidAmount = None, | ||
ecdh_pubkey: bytes = None, | ||
ecdh_privkey: bytes = None, | ||
script_pubkey: bytes = None, | ||
random_seed32: bytes = None, | ||
) -> None: | ||
self.amount = amount | ||
self.ecdh_pubkey = ecdh_pubkey | ||
self.ecdh_privkey = ecdh_privkey | ||
self.script_pubkey = script_pubkey | ||
self.random_seed32 = random_seed32 | ||
|
||
@classmethod | ||
def get_fields(cls) -> Dict: | ||
return { | ||
1: ('amount', LiquidAmount, 0), | ||
2: ('ecdh_pubkey', p.BytesType, 0), | ||
3: ('ecdh_privkey', p.BytesType, 0), | ||
4: ('script_pubkey', p.BytesType, 0), | ||
5: ('random_seed32', p.BytesType, 0), | ||
} |
Oops, something went wrong.