├── py ├── .gitignore ├── example ├── swap ├── examples ├── submit.py ├── wallet.py ├── simple_tx.py ├── multisig_tx.py └── swap.py ├── _cffi_build ├── secp256k1_generator.h ├── secp256k1_recovery.h ├── secp256k1_rangeproof.h ├── secp256k1_commitment.h ├── secp256k1_aggsig.h ├── secp256k1_bulletproofs.h └── secp256k1.h ├── GrinSwap.sol ├── secp256k1 ├── build.py ├── __init__.py ├── aggsig.py ├── key.py └── pedersen.py ├── README.md ├── grin ├── aggsig.py ├── proof.py ├── slate.py ├── util.py ├── wallet.py ├── keychain.py ├── btc.py ├── transaction.py └── extkey.py ├── tests ├── test_aggsig.py ├── test_bulletproof.py ├── test_key.py └── test_extkey.py ├── example.py └── LICENSE /py: -------------------------------------------------------------------------------- 1 | export LD_LIBRARY_PATH=$PWD/lib 2 | python "$@" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /include 3 | /lib 4 | /logs 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /example: -------------------------------------------------------------------------------- 1 | export LD_LIBRARY_PATH=$PWD/lib 2 | python example.py "$@" 3 | -------------------------------------------------------------------------------- /swap: -------------------------------------------------------------------------------- 1 | export LD_LIBRARY_PATH=$PWD/lib 2 | python example.py swap "$@" 3 | -------------------------------------------------------------------------------- /examples/submit.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | 3 | 4 | def submit(file: str, node_url: str): 5 | fluff = True 6 | 7 | f = open(file, "r") 8 | raw = f.read() 9 | f.close() 10 | 11 | print("Submitting to node..") 12 | 13 | urlopen("{}/v1/pool/push".format(node_url) + ("?fluff" if fluff else ""), raw.encode(), 60) 14 | 15 | print("Transaction complete!") 16 | -------------------------------------------------------------------------------- /examples/wallet.py: -------------------------------------------------------------------------------- 1 | from secp256k1 import FLAG_ALL 2 | from secp256k1.pedersen import Secp256k1 3 | from grin.util import GRIN_UNIT 4 | from grin.wallet import Wallet 5 | 6 | 7 | def outputs(): 8 | secp = Secp256k1(None, FLAG_ALL) 9 | wallet = Wallet.open(secp, "wallet_a") 10 | for entry in wallet.outputs.values(): 11 | print("n={} key={} value={} commit={}".format(entry.n_child, entry.key_id, 12 | entry.value / GRIN_UNIT, wallet.commit(entry))) -------------------------------------------------------------------------------- /_cffi_build/secp256k1_generator.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | unsigned char data[64]; 3 | } secp256k1_generator; 4 | 5 | int secp256k1_generator_parse( 6 | const secp256k1_context* ctx, 7 | secp256k1_generator* commit, 8 | const unsigned char *input 9 | ); 10 | 11 | int secp256k1_generator_serialize( 12 | const secp256k1_context* ctx, 13 | unsigned char *output, 14 | const secp256k1_generator* commit 15 | ); 16 | 17 | int secp256k1_generator_generate( 18 | const secp256k1_context* ctx, 19 | secp256k1_generator* gen, 20 | const unsigned char *seed32 21 | ); 22 | 23 | int secp256k1_generator_generate_blinded( 24 | const secp256k1_context* ctx, 25 | secp256k1_generator* gen, 26 | const unsigned char *key32, 27 | const unsigned char *blind32 28 | ); 29 | -------------------------------------------------------------------------------- /GrinSwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.13; 2 | 3 | contract GrinSwap { 4 | address public sign_address; 5 | address public receive_address; 6 | address public refund_address; 7 | uint public unlock_time; 8 | 9 | constructor(address _sign_address, address _receive_address) public { 10 | sign_address = _sign_address; 11 | receive_address = _receive_address; 12 | refund_address = msg.sender; 13 | unlock_time = block.timestamp + 24 hours; 14 | } 15 | 16 | function claim(bytes32 r, bytes32 s, uint8 v) public { 17 | require(ecrecover("", v, r, s) == sign_address); 18 | selfdestruct(receive_address); 19 | } 20 | 21 | function refund() public { 22 | require(block.timestamp >= unlock_time); 23 | selfdestruct(refund_address); 24 | } 25 | 26 | function () external payable {} 27 | } -------------------------------------------------------------------------------- /secp256k1/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections import namedtuple 3 | from cffi import FFI 4 | 5 | 6 | def absolute(*paths): 7 | op = os.path 8 | return op.realpath(op.abspath(op.join(op.dirname(__file__), "..", *paths))) 9 | 10 | Source = namedtuple("Source", ("h", "include")) 11 | 12 | _modules = [ 13 | "secp256k1", 14 | "secp256k1_recovery", 15 | "secp256k1_aggsig", 16 | "secp256k1_generator", 17 | "secp256k1_commitment", 18 | "secp256k1_rangeproof", 19 | "secp256k1_bulletproofs" 20 | ] 21 | 22 | ffi = FFI() 23 | code = [] 24 | for mod in _modules: 25 | with open(absolute("_cffi_build/{}.h".format(mod)), "rt") as h: 26 | ffi.cdef(h.read()) 27 | code.append("#include <{}.h>".format(mod)) 28 | ffi.set_source('_libsecp256k1', "\n".join(code), libraries=["secp256k1"], library_dirs=[absolute("lib")], 29 | include_dirs=[absolute("include")]) 30 | ffi.compile(verbose=True) 31 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1_recovery.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | unsigned char data[65]; 3 | } secp256k1_ecdsa_recoverable_signature; 4 | 5 | int secp256k1_ecdsa_recoverable_signature_parse_compact( 6 | const secp256k1_context* ctx, 7 | secp256k1_ecdsa_recoverable_signature* sig, 8 | const unsigned char *input64, 9 | int recid 10 | ); 11 | 12 | int secp256k1_ecdsa_recoverable_signature_convert( 13 | const secp256k1_context* ctx, 14 | secp256k1_ecdsa_signature* sig, 15 | const secp256k1_ecdsa_recoverable_signature* sigin 16 | ); 17 | 18 | int secp256k1_ecdsa_recoverable_signature_serialize_compact( 19 | const secp256k1_context* ctx, 20 | unsigned char *output64, 21 | int *recid, 22 | const secp256k1_ecdsa_recoverable_signature* sig 23 | ); 24 | 25 | int secp256k1_ecdsa_sign_recoverable( 26 | const secp256k1_context* ctx, 27 | secp256k1_ecdsa_recoverable_signature *sig, 28 | const unsigned char *msg32, 29 | const unsigned char *seckey, 30 | secp256k1_nonce_function noncefp, 31 | const void *ndata 32 | ); 33 | 34 | int secp256k1_ecdsa_recover( 35 | const secp256k1_context* ctx, 36 | secp256k1_pubkey *pubkey, 37 | const secp256k1_ecdsa_recoverable_signature *sig, 38 | const unsigned char *msg32 39 | ); 40 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1_rangeproof.h: -------------------------------------------------------------------------------- 1 | int secp256k1_rangeproof_verify( 2 | const secp256k1_context* ctx, 3 | uint64_t *min_value, 4 | uint64_t *max_value, 5 | const secp256k1_pedersen_commitment *commit, 6 | const unsigned char *proof, 7 | size_t plen, 8 | const unsigned char *extra_commit, 9 | size_t extra_commit_len, 10 | const secp256k1_generator* gen 11 | ); 12 | 13 | int secp256k1_rangeproof_rewind( 14 | const secp256k1_context* ctx, 15 | unsigned char *blind_out, 16 | uint64_t *value_out, 17 | unsigned char *message_out, 18 | size_t *outlen, 19 | const unsigned char *nonce, 20 | uint64_t *min_value, 21 | uint64_t *max_value, 22 | const secp256k1_pedersen_commitment *commit, 23 | const unsigned char *proof, 24 | size_t plen, 25 | const unsigned char *extra_commit, 26 | size_t extra_commit_len, 27 | const secp256k1_generator *gen 28 | ); 29 | 30 | int secp256k1_rangeproof_sign( 31 | const secp256k1_context* ctx, 32 | unsigned char *proof, 33 | size_t *plen, 34 | uint64_t min_value, 35 | const secp256k1_pedersen_commitment *commit, 36 | const unsigned char *blind, 37 | const unsigned char *nonce, 38 | int exp, 39 | int min_bits, 40 | uint64_t value, 41 | const unsigned char *message, 42 | size_t msg_len, 43 | const unsigned char *extra_commit, 44 | size_t extra_commit_len, 45 | const secp256k1_generator *gen 46 | ); 47 | 48 | int secp256k1_rangeproof_info( 49 | const secp256k1_context* ctx, 50 | int *exp, 51 | int *mantissa, 52 | uint64_t *min_value, 53 | uint64_t *max_value, 54 | const unsigned char *proof, 55 | size_t plen 56 | ); 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GrinSwap proof of concept 2 | This code provides a proof of concept for executing Grin-BTC/ETH atomic swaps. It was used to perform the first swaps on Grin testnet (as far as we know), read more about it [here](https://medium.com/grinswap/first-grin-atomic-swap-a16b4cc19196). 3 | Note that this code is definitely not production-ready and usage is at your own risk. 4 | 5 | ## Building 6 | Getting this code to run is a bit involved, it has not been optimized for user friendliness. The steps to take are: 7 | 1. Download a copy of this repository 8 | 2. Clone [the Grin fork](https://github.com/mimblewimble/secp256k1-zkp) of secp256k1-zkp 9 | 3. Copy `build_secp256k1-zkp.sh` to the secp25k1-zkp directory 10 | 4. Excute it to build the library 11 | 5. Copy the `build/include` and `build/lib` directories to the grinswap directory 12 | 6. Create and start a venv with python 3.6, and install `cffi eth-hash pysha3` with pip 13 | 7. Execute `./build` to build the python cffi library 14 | 15 | ## Usage 16 | Executing a swap is for now a very manual process, requiring sending files between the seller and buyer multiple times. 17 | 1. Start the venv 18 | 2. Create a wallet directory and add a `wallet.seed` file with a random seed 19 | 3. If you are selling Grin, you need to have some in your wallet. Run `./example simple_tx receive` and send some Grin to it, on port 17175 20 | 4. To get started with the swap, run `./swap sell` and follow the directions 21 | Any Grin transactions can be submitted with `./example ` 22 | 23 | ## License 24 | This code is released under the Apache License 2.0 25 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1_commitment.h: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | unsigned char data[64]; 3 | } secp256k1_pedersen_commitment; 4 | 5 | int secp256k1_pedersen_commitment_parse( 6 | const secp256k1_context* ctx, 7 | secp256k1_pedersen_commitment* commit, 8 | const unsigned char *input 9 | ); 10 | 11 | int secp256k1_pedersen_commitment_serialize( 12 | const secp256k1_context* ctx, 13 | unsigned char *output, 14 | const secp256k1_pedersen_commitment* commit 15 | ); 16 | 17 | int secp256k1_pedersen_commit( 18 | const secp256k1_context* ctx, 19 | secp256k1_pedersen_commitment *commit, 20 | const unsigned char *blind, 21 | uint64_t value, 22 | const secp256k1_generator *value_gen, 23 | const secp256k1_generator *blind_gen 24 | ); 25 | 26 | int secp256k1_pedersen_blind_sum( 27 | const secp256k1_context* ctx, 28 | unsigned char *blind_out, 29 | const unsigned char * const *blinds, 30 | size_t n, 31 | size_t npositive 32 | ); 33 | 34 | int secp256k1_pedersen_commit_sum( 35 | const secp256k1_context* ctx, 36 | secp256k1_pedersen_commitment *commit_out, 37 | const secp256k1_pedersen_commitment * const* commits, 38 | size_t pcnt, 39 | const secp256k1_pedersen_commitment * const* ncommits, 40 | size_t ncnt 41 | ); 42 | 43 | int secp256k1_pedersen_verify_tally( 44 | const secp256k1_context* ctx, 45 | const secp256k1_pedersen_commitment * const* pos, 46 | size_t n_pos, 47 | const secp256k1_pedersen_commitment * const* neg, 48 | size_t n_neg 49 | ); 50 | 51 | int secp256k1_pedersen_blind_generator_blind_sum( 52 | const secp256k1_context* ctx, 53 | const uint64_t *value, 54 | const unsigned char* const* generator_blind, 55 | unsigned char* const* blinding_factor, 56 | size_t n_total, 57 | size_t n_inputs 58 | ); 59 | 60 | int secp256k1_pedersen_commitment_to_pubkey( 61 | const secp256k1_context* ctx, 62 | secp256k1_pubkey* pubkey, 63 | const secp256k1_pedersen_commitment* commit 64 | ); -------------------------------------------------------------------------------- /secp256k1/__init__.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from ._libsecp256k1 import ffi, lib 3 | 4 | EC_COMPRESSED = lib.SECP256K1_EC_COMPRESSED 5 | EC_UNCOMPRESSED = lib.SECP256K1_EC_UNCOMPRESSED 6 | 7 | FLAG_NONE = lib.SECP256K1_CONTEXT_NONE 8 | FLAG_SIGN = lib.SECP256K1_CONTEXT_SIGN 9 | FLAG_VERIFY = lib.SECP256K1_CONTEXT_VERIFY 10 | FLAG_ALL = FLAG_SIGN | FLAG_VERIFY 11 | 12 | HAS_AGGSIG = True 13 | HAS_BULLETPROOFS = False 14 | HAS_COMMITMENT = False 15 | HAS_ECDH = False 16 | HAS_GENERATOR = False 17 | HAS_RANGEPROOF = False 18 | HAS_RECOVERY = True 19 | 20 | MESSAGE_SIZE = 32 21 | SECRET_KEY_SIZE = 32 22 | PUBLIC_KEY_SIZE = 65 23 | PUBLIC_KEY_SIZE_COMPRESSED = 33 24 | 25 | 26 | class Secp256k1: 27 | def __init__(self, ctx, flags): 28 | self._destroy = None 29 | if ctx is None: 30 | assert flags in (FLAG_NONE, FLAG_SIGN, FLAG_VERIFY, FLAG_ALL) 31 | ctx = lib.secp256k1_context_create(flags) 32 | self.flags = flags 33 | self.ctx = ctx 34 | 35 | def __del__(self): 36 | if not hasattr(self, "_destroy"): 37 | return 38 | 39 | if self._destroy and self.ctx: 40 | self._destroy(self.ctx) 41 | self.ctx = None 42 | 43 | 44 | class Message: 45 | def __init__(self, message: bytearray): 46 | assert len(message) == MESSAGE_SIZE, "Invalid message size" 47 | self.message = message 48 | 49 | def __eq__(self, other): 50 | return isinstance(other, Message) and self.message == other.message 51 | 52 | def __str__(self): 53 | return "Message<{}>".format(self.to_hex().decode()) 54 | 55 | def __repr__(self): 56 | return self.__str__() 57 | 58 | def to_bytearray(self) -> bytearray: 59 | message = self.message[:] 60 | return message 61 | 62 | def to_hex(self) -> bytes: 63 | return hexlify(self.message) 64 | 65 | @staticmethod 66 | def from_bytearray(data: bytearray): 67 | message = bytearray([0] * MESSAGE_SIZE) 68 | for i in range(min(MESSAGE_SIZE, len(data))): 69 | message[i] = data[i] 70 | return Message(message) 71 | -------------------------------------------------------------------------------- /secp256k1/aggsig.py: -------------------------------------------------------------------------------- 1 | from os import urandom 2 | from typing import List, Optional 3 | from secp256k1 import Secp256k1, Message 4 | from secp256k1.key import SecretKey, PublicKey, Signature 5 | from ._libsecp256k1 import ffi, lib 6 | 7 | 8 | def sign_single(secp: Secp256k1, message: Message, secret_key: SecretKey, secret_nonce: Optional[SecretKey], 9 | public_nonce: Optional[PublicKey], public_key_sum: Optional[PublicKey], 10 | public_nonce_sum: Optional[PublicKey], extra_secret_key: Optional[SecretKey]) -> Signature: 11 | signature_out = ffi.new("char [64]") 12 | res = lib.secp256k1_aggsig_sign_single( 13 | secp.ctx, signature_out, bytes(message.message), bytes(secret_key.key), ffi.NULL if secret_nonce is None 14 | else bytes(secret_nonce.key), ffi.NULL if extra_secret_key is None else bytes(extra_secret_key.key), 15 | ffi.NULL if public_nonce is None else public_nonce.key, ffi.NULL if public_nonce_sum is None 16 | else public_nonce_sum.key, ffi.NULL if public_key_sum is None else public_key_sum.key, urandom(32) 17 | ) 18 | assert res, "Unable to sign message" 19 | return Signature.from_bytearray(secp, bytearray(ffi.buffer(signature_out, 64))) 20 | 21 | 22 | def verify_single(secp: Secp256k1, signature: Signature, message: Message, public_key: PublicKey, 23 | public_key_sum: Optional[PublicKey], public_nonce_sum: Optional[PublicKey], 24 | extra_public_key: Optional[PublicKey], partial=True) -> bool: 25 | res = lib.secp256k1_aggsig_verify_single( 26 | secp.ctx, bytes(signature.signature), bytes(message.message), ffi.NULL if public_nonce_sum is None 27 | else public_nonce_sum.key, public_key.key, ffi.NULL if public_key_sum is None else public_key_sum.key, 28 | ffi.NULL if extra_public_key is None else extra_public_key.key, 1 if partial else 0 29 | ) 30 | return True if res else False 31 | 32 | 33 | def add_single(secp: Secp256k1, signatures: List[Signature], public_nonce_sum: PublicKey) -> Signature: 34 | signature_out = ffi.new("char [64]") 35 | signature_ptrs = [] 36 | for signature in signatures: 37 | signature_ptr = ffi.new("char []", bytes(signature.signature)) 38 | signature_ptrs.append(signature_ptr) 39 | 40 | res = lib.secp256k1_aggsig_add_signatures_single( 41 | secp.ctx, signature_out, signature_ptrs, len(signatures), public_nonce_sum.key 42 | ) 43 | assert res, "Unable to add signatures" 44 | return Signature.from_bytearray(secp, bytearray(ffi.buffer(signature_out, 64))) 45 | -------------------------------------------------------------------------------- /grin/aggsig.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from secp256k1 import Secp256k1, Message 3 | from secp256k1.key import SecretKey, PublicKey, Signature 4 | import secp256k1.aggsig as aggsig 5 | from grin.util import kernel_sig_msg 6 | 7 | 8 | def calculate_partial(secp: Secp256k1, excess: SecretKey, nonce: SecretKey, public_excess_sum: PublicKey, 9 | public_nonce_sum: PublicKey, fee: int, lock_height: int) -> Signature: 10 | message = Message.from_bytearray(kernel_sig_msg(fee, lock_height)) 11 | return aggsig.sign_single( 12 | secp, message, excess, nonce, public_nonce_sum, public_excess_sum, public_nonce_sum, None 13 | ) 14 | 15 | 16 | def verify_partial(secp: Secp256k1, signature: Signature, public_excess: PublicKey, public_excess_sum: PublicKey, 17 | public_nonce_sum: PublicKey, fee: int, lock_height: int) -> bool: 18 | message = Message.from_bytearray(kernel_sig_msg(fee, lock_height)) 19 | return aggsig.verify_single( 20 | secp, signature, message, public_excess, public_excess_sum, public_nonce_sum, None, True 21 | ) 22 | 23 | 24 | def add_partials(secp: Secp256k1, partials: List[Signature], public_nonce_sum: PublicKey) -> Signature: 25 | return aggsig.add_single(secp, partials, public_nonce_sum) 26 | 27 | 28 | def verify(secp: Secp256k1, signature: Signature, public_excess_sum: PublicKey, fee: int, lock_height: int) -> bool: 29 | message = Message.from_bytearray(kernel_sig_msg(fee, lock_height)) 30 | return aggsig.verify_single( 31 | secp, signature, message, public_excess_sum, public_excess_sum, None, None, False 32 | ) 33 | 34 | 35 | def calculate_partial_adaptor(secp: Secp256k1, excess: SecretKey, nonce: SecretKey, extra: SecretKey, 36 | public_excess_sum: PublicKey, public_nonce_sum: PublicKey, fee: int, 37 | lock_height: int) -> Signature: 38 | message = Message.from_bytearray(kernel_sig_msg(fee, lock_height)) 39 | return aggsig.sign_single( 40 | secp, message, excess, nonce, public_nonce_sum, public_excess_sum, public_nonce_sum, extra 41 | ) 42 | 43 | 44 | def verify_partial_adaptor(secp: Secp256k1, signature: Signature, public_excess: PublicKey, extra: PublicKey, 45 | public_excess_sum: PublicKey, public_nonce_sum: PublicKey, fee: int, 46 | lock_height: int) -> bool: 47 | message = Message.from_bytearray(kernel_sig_msg(fee, lock_height)) 48 | return aggsig.verify_single( 49 | secp, signature, message, public_excess, public_excess_sum, public_nonce_sum, extra, True 50 | ) 51 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1_aggsig.h: -------------------------------------------------------------------------------- 1 | typedef struct secp256k1_aggsig_context_struct secp256k1_aggsig_context; 2 | 3 | typedef struct { 4 | unsigned char data[32]; 5 | } secp256k1_aggsig_partial_signature; 6 | 7 | secp256k1_aggsig_context* secp256k1_aggsig_context_create( 8 | const secp256k1_context *ctx, 9 | const secp256k1_pubkey *pubkeys, 10 | size_t n_pubkeys, 11 | const unsigned char *seed 12 | ); 13 | 14 | void secp256k1_aggsig_context_destroy( 15 | secp256k1_aggsig_context *aggctx 16 | ); 17 | 18 | int secp256k1_aggsig_generate_nonce( 19 | const secp256k1_context* ctx, 20 | secp256k1_aggsig_context* aggctx, 21 | size_t index 22 | ); 23 | 24 | int secp256k1_aggsig_export_secnonce_single( 25 | const secp256k1_context* ctx, 26 | unsigned char* secnonce32, 27 | const unsigned char* seed 28 | ); 29 | 30 | int secp256k1_aggsig_sign_single( 31 | const secp256k1_context* ctx, 32 | unsigned char *sig64, 33 | const unsigned char *msg32, 34 | const unsigned char *seckey32, 35 | const unsigned char* secnonce32, 36 | const unsigned char* extra32, 37 | const secp256k1_pubkey *pubnonce_for_e, 38 | const secp256k1_pubkey* pubnonce_total, 39 | const secp256k1_pubkey* pubkey_for_e, 40 | const unsigned char* seed 41 | ); 42 | 43 | int secp256k1_aggsig_partial_sign( 44 | const secp256k1_context* ctx, 45 | secp256k1_aggsig_context* aggctx, 46 | secp256k1_aggsig_partial_signature *partial, 47 | const unsigned char *msg32, 48 | const unsigned char *seckey32, 49 | size_t index 50 | ); 51 | 52 | int secp256k1_aggsig_combine_signatures( 53 | const secp256k1_context* ctx, 54 | secp256k1_aggsig_context* aggctx, 55 | unsigned char *sig64, 56 | const secp256k1_aggsig_partial_signature *partial, 57 | size_t n_sigs 58 | ); 59 | 60 | int secp256k1_aggsig_add_signatures_single( 61 | const secp256k1_context* ctx, 62 | unsigned char *sig64, 63 | const unsigned char** sigs, 64 | size_t num_sigs, 65 | const secp256k1_pubkey* pubnonce_total 66 | ); 67 | 68 | int secp256k1_aggsig_verify_single( 69 | const secp256k1_context* ctx, 70 | const unsigned char *sig64, 71 | const unsigned char *msg32, 72 | const secp256k1_pubkey *pubnonce, 73 | const secp256k1_pubkey *pubkey, 74 | const secp256k1_pubkey *pubkey_total, 75 | const secp256k1_pubkey *extra_pubkey, 76 | const int is_partial 77 | ); 78 | 79 | int secp256k1_aggsig_verify( 80 | const secp256k1_context* ctx, 81 | secp256k1_scratch_space* scratch, 82 | const unsigned char *sig64, 83 | const unsigned char *msg32, 84 | const secp256k1_pubkey *pubkeys, 85 | size_t n_pubkeys 86 | ); 87 | 88 | int secp256k1_aggsig_build_scratch_and_verify( 89 | const secp256k1_context* ctx, 90 | const unsigned char *sig64, 91 | const unsigned char *msg32, 92 | const secp256k1_pubkey *pubkeys, 93 | size_t n_pubkeys 94 | ); 95 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1_bulletproofs.h: -------------------------------------------------------------------------------- 1 | typedef struct secp256k1_bulletproof_generators secp256k1_bulletproof_generators; 2 | #define SECP256K1_BULLETPROOF_MAX_DEPTH ... 3 | #define SECP256K1_BULLETPROOF_MAX_PROOF ... 4 | 5 | secp256k1_bulletproof_generators *secp256k1_bulletproof_generators_create( 6 | const secp256k1_context* ctx, 7 | const secp256k1_generator *blinding_gen, 8 | size_t n 9 | ); 10 | 11 | void secp256k1_bulletproof_generators_destroy( 12 | const secp256k1_context* ctx, 13 | secp256k1_bulletproof_generators *gen 14 | ); 15 | 16 | int secp256k1_bulletproof_rangeproof_verify( 17 | const secp256k1_context* ctx, 18 | secp256k1_scratch_space* scratch, 19 | const secp256k1_bulletproof_generators *gens, 20 | const unsigned char* proof, 21 | size_t plen, 22 | const uint64_t* min_value, 23 | const secp256k1_pedersen_commitment* commit, 24 | size_t n_commits, 25 | size_t nbits, 26 | const secp256k1_generator* value_gen, 27 | const unsigned char* extra_commit, 28 | size_t extra_commit_len 29 | ); 30 | 31 | int secp256k1_bulletproof_rangeproof_verify_multi( 32 | const secp256k1_context* ctx, 33 | secp256k1_scratch_space* scratch, 34 | const secp256k1_bulletproof_generators *gens, 35 | const unsigned char* const* proof, 36 | size_t n_proofs, 37 | size_t plen, 38 | const uint64_t* const* min_value, 39 | const secp256k1_pedersen_commitment* const* commit, 40 | size_t n_commits, 41 | size_t nbits, 42 | const secp256k1_generator* value_gen, 43 | const unsigned char* const* extra_commit, 44 | size_t *extra_commit_len 45 | ); 46 | 47 | int secp256k1_bulletproof_rangeproof_rewind( 48 | const secp256k1_context* ctx, 49 | const secp256k1_bulletproof_generators* gens, 50 | uint64_t* value, 51 | unsigned char* blind, 52 | const unsigned char* proof, 53 | size_t plen, 54 | uint64_t min_value, 55 | const secp256k1_pedersen_commitment* commit, 56 | const secp256k1_generator* value_gen, 57 | const unsigned char* nonce, 58 | const unsigned char* extra_commit, 59 | size_t extra_commit_len, 60 | unsigned char* message 61 | ); 62 | 63 | int secp256k1_bulletproof_rangeproof_prove( 64 | const secp256k1_context* ctx, 65 | secp256k1_scratch_space* scratch, 66 | const secp256k1_bulletproof_generators* gens, 67 | unsigned char* proof, 68 | size_t* plen, 69 | unsigned char* tau_x, 70 | secp256k1_pubkey* t_one, 71 | secp256k1_pubkey* t_two, 72 | const uint64_t* value, 73 | const uint64_t* min_value, 74 | const unsigned char* const* blind, 75 | const secp256k1_pedersen_commitment* const* commits, 76 | size_t n_commits, 77 | const secp256k1_generator* value_gen, 78 | size_t nbits, 79 | const unsigned char* nonce, 80 | const unsigned char* private_nonce, 81 | const unsigned char* extra_commit, 82 | size_t extra_commit_len, 83 | const unsigned char* message 84 | ); 85 | -------------------------------------------------------------------------------- /tests/test_aggsig.py: -------------------------------------------------------------------------------- 1 | from os import urandom 2 | from random import randint 3 | from secp256k1 import FLAG_ALL 4 | from secp256k1.key import SecretKey, PublicKey, Signature 5 | from secp256k1.pedersen import Secp256k1 6 | from grin import aggsig 7 | 8 | 9 | def test_partial_sig(): 10 | secp = Secp256k1(None, FLAG_ALL) 11 | excess = SecretKey.random(secp) 12 | public_excess = excess.to_public_key(secp) 13 | nonce = SecretKey.random(secp) 14 | public_key_sum = SecretKey.random(secp).to_public_key(secp) 15 | public_nonce_sum = SecretKey.random(secp).to_public_key(secp) 16 | fee = randint(1, 999999) 17 | lock_height = randint(1, 999999) 18 | sig = aggsig.calculate_partial(secp, excess, nonce, public_key_sum, public_nonce_sum, fee, lock_height) 19 | 20 | assert aggsig.verify_partial(secp, sig, public_excess, public_key_sum, public_nonce_sum, fee, lock_height) 21 | rnd_sig = Signature(bytearray(urandom(64))) 22 | assert not aggsig.verify_partial(secp, rnd_sig, public_excess, public_key_sum, public_nonce_sum, fee, lock_height) 23 | public_rnd = SecretKey.random(secp).to_public_key(secp) 24 | assert not aggsig.verify_partial(secp, sig, public_rnd, public_key_sum, public_nonce_sum, fee, lock_height) 25 | assert not aggsig.verify_partial(secp, sig, public_excess, public_rnd, public_nonce_sum, fee, lock_height) 26 | assert not aggsig.verify_partial(secp, sig, public_excess, public_key_sum, public_rnd, fee, lock_height) 27 | assert not aggsig.verify_partial(secp, sig, public_excess, public_key_sum, public_nonce_sum, 0, lock_height) 28 | assert not aggsig.verify_partial(secp, sig, public_excess, public_key_sum, public_nonce_sum, fee, 0) 29 | 30 | 31 | def test_sig(): 32 | # Test Grin-like signature scheme 33 | secp = Secp256k1(None, FLAG_ALL) 34 | nonce_a = SecretKey.random(secp) 35 | public_nonce_a = nonce_a.to_public_key(secp) 36 | nonce_b = SecretKey.random(secp) 37 | public_nonce_b = nonce_b.to_public_key(secp) 38 | public_nonce_sum = PublicKey.from_combination(secp, [public_nonce_a, public_nonce_b]) 39 | excess_a = SecretKey.random(secp) 40 | public_excess_a = excess_a.to_public_key(secp) 41 | excess_b = SecretKey.random(secp) 42 | public_excess_b = excess_b.to_public_key(secp) 43 | public_excess_sum = PublicKey.from_combination(secp, [public_excess_a, public_excess_b]) 44 | fee = randint(1, 999999) 45 | lock_height = randint(1, 999999) 46 | 47 | # Partial signature for A 48 | sig_a = aggsig.calculate_partial(secp, excess_a, nonce_a, public_excess_sum, public_nonce_sum, fee, lock_height) 49 | assert aggsig.verify_partial(secp, sig_a, public_excess_a, public_excess_sum, public_nonce_sum, fee, lock_height) 50 | 51 | # Partial signature for B 52 | sig_b = aggsig.calculate_partial(secp, excess_b, nonce_b, public_excess_sum, public_nonce_sum, fee, lock_height) 53 | assert aggsig.verify_partial(secp, sig_b, public_excess_b, public_excess_sum, public_nonce_sum, fee, lock_height) 54 | 55 | # Total signature 56 | sig = aggsig.add_partials(secp, [sig_a, sig_b], public_nonce_sum) 57 | assert aggsig.verify(secp, sig, public_excess_sum, fee, lock_height) 58 | rnd_sig = Signature(bytearray(urandom(64))) 59 | assert not aggsig.verify(secp, rnd_sig, public_excess_sum, fee, lock_height) 60 | public_rnd = SecretKey.random(secp).to_public_key(secp) 61 | assert not aggsig.verify(secp, sig, public_rnd, fee, lock_height) 62 | assert not aggsig.verify(secp, sig, public_excess_sum, 0, lock_height) 63 | assert not aggsig.verify(secp, sig, public_excess_sum, fee, 0) 64 | -------------------------------------------------------------------------------- /grin/proof.py: -------------------------------------------------------------------------------- 1 | from hashlib import blake2b 2 | from secp256k1.key import SecretKey, PublicKey 3 | from secp256k1.pedersen import Secp256k1, Commitment, RangeProof 4 | from grin.extkey import ChainCode, ExtendedSecretKey 5 | from grin.keychain import ChildKey 6 | 7 | 8 | def create_nonce(secp: Secp256k1, chain_code: ChainCode, commit: Commitment) -> SecretKey: 9 | return SecretKey.from_bytearray(secp, bytearray( 10 | blake2b(bytes(chain_code.chain_code), digest_size=32, key=bytes(commit.to_bytearray(secp))).digest() 11 | )) 12 | 13 | 14 | def create_common_nonce(secp: Secp256k1, secret_key: SecretKey, public_key: PublicKey, commit: Commitment) -> SecretKey: 15 | common_key = public_key.mul(secp, secret_key) 16 | return SecretKey.from_bytearray(secp, bytearray( 17 | blake2b(bytes(common_key.to_bytearray(secp)), digest_size=32, key=bytes(commit.to_bytearray(secp))).digest() 18 | )) 19 | 20 | 21 | def create(secp: Secp256k1, ext_key: ExtendedSecretKey, amount: int, 22 | commit: Commitment, extra_data: bytearray) -> RangeProof: 23 | nonce = create_nonce(secp, ext_key.chain_code, commit) 24 | return secp.bullet_proof(amount, ext_key.secret_key, nonce, extra_data) 25 | 26 | 27 | def verify(secp: Secp256k1, commit: Commitment, proof: RangeProof, extra_data: bytearray) -> bool: 28 | return secp.verify_bullet_proof(commit, proof, extra_data) 29 | 30 | 31 | class MultiPartyBulletProof: 32 | def __init__(self, secp: Secp256k1, ext_key: ExtendedSecretKey, amount: int, 33 | commit: Commitment, common_nonce: SecretKey): 34 | self.secp = secp 35 | self.key = ext_key.secret_key 36 | self.amount = amount 37 | self.commit = commit 38 | self.nonce = create_nonce(secp, ext_key.chain_code, commit) 39 | self.common_nonce = common_nonce 40 | self.t_1 = None 41 | self.t_2 = None 42 | self.tau_x = None 43 | 44 | def round_1(self) -> (PublicKey, PublicKey): 45 | self.t_1, self.t_2 = self.secp.bullet_proof_multisig_1(self.amount, self.key, self.commit, 46 | self.common_nonce, self.nonce, bytearray()) 47 | return self.t_1.clone(self.secp), self.t_2.clone(self.secp) 48 | 49 | def fill_round_1(self, t_1: PublicKey, t_2: PublicKey): 50 | if self.t_1 is None: 51 | self.t_1 = t_1.clone(self.secp) 52 | else: 53 | self.t_1.add_assign(self.secp, t_1) 54 | 55 | if self.t_2 is None: 56 | self.t_2 = t_2.clone(self.secp) 57 | else: 58 | self.t_2.add_assign(self.secp, t_2) 59 | 60 | def round_2(self) -> SecretKey: 61 | self.tau_x = self.secp.bullet_proof_multisig_2(self.amount, self.key, self.commit, self.common_nonce, 62 | self.nonce, self.t_1, self.t_2, bytearray()) 63 | return self.tau_x.clone() 64 | 65 | def fill_round_2(self, tau_x: SecretKey): 66 | if self.tau_x is None: 67 | self.tau_x = tau_x 68 | else: 69 | self.tau_x.add_assign(self.secp, tau_x) 70 | 71 | def finalize(self) -> RangeProof: 72 | return self.secp.bullet_proof_multisig_3(self.amount, self.key, self.commit, self.common_nonce, 73 | self.nonce, self.t_1, self.t_2, self.tau_x, bytearray()) 74 | 75 | 76 | class TwoPartyBulletProof(MultiPartyBulletProof): 77 | def __init__(self, secp: Secp256k1, ext_key: ExtendedSecretKey, public_key: PublicKey, 78 | amount: int, commit: Commitment): 79 | common_nonce = create_common_nonce(secp, ext_key.secret_key, public_key, commit) 80 | super().__init__(secp, ext_key, amount, commit, common_nonce) 81 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from examples import simple_tx, multisig_tx, submit, swap, wallet 2 | from sys import argv 3 | 4 | 5 | def main(): 6 | if len(argv) < 2: 7 | print("################################\n" 8 | "# GrinSwap examples\n" 9 | "#\n" 10 | "# Usage: run './py example.py ' to find out more. Choices for are\n" 11 | "# - simple_tx, create and submit a grin transaction\n" 12 | "# - multisig_tx, create a 2-of-2 multi-signature output\n" 13 | "# - submit, submit a finished transaction to a node\n" 14 | "################################") 15 | return 16 | 17 | if argv[1] == "simple_tx": 18 | if len(argv) < 3: 19 | print("################################\n" 20 | "# Sending and receiving a simple transaction\n" 21 | "#\n" 22 | "# Requirements:\n" 23 | "# - Wallet folder 'wallet_1' with at least 1 unspent output\n" 24 | "# - Wallet folder 'wallet_2', receiving wallet\n" 25 | "# - A node to submit the final tx to\n" 26 | "#\n" 27 | "# Usage:\n" 28 | "# - Run './example simple_tx receive' to start the receive server\n" 29 | "# - With the server running, execute './example simple_tx send '\n" 30 | "################################") 31 | return 32 | 33 | if argv[2] == "receive": 34 | simple_tx.receive() 35 | elif argv[2] == "send": 36 | if len(argv) < 4: 37 | node = "http://127.0.0.1:13413" 38 | print("Missing node URL, assuming '{}'".format(node)) 39 | else: 40 | node = argv[3] 41 | simple_tx.send(node) 42 | elif argv[1] == "multisig_tx": 43 | if len(argv) < 3: 44 | print("################################\n" 45 | "# Building a multi-signature transaction with timelocked refund\n" 46 | "#\n" 47 | "# Requirements:\n" 48 | "# - Wallet folder 'wallet_1' with at least 1 unspent output\n" 49 | "# - Wallet folder 'wallet_2', receiving wallet\n" 50 | "# - A node to submit the final tx to\n" 51 | "#\n" 52 | "# Usage:\n" 53 | "# - Run './example multisig_tx receive' to start the receive server\n" 54 | "# - With the server running, execute './example multisig_tx send '\n" 55 | "################################") 56 | return 57 | 58 | if argv[2] == "receive": 59 | multisig_tx.receive() 60 | elif argv[2] == "send": 61 | if len(argv) < 4: 62 | node = "http://127.0.0.1:13413" 63 | print("Missing node URL, assuming '{}'".format(node)) 64 | else: 65 | node = argv[3] 66 | multisig_tx.send(node) 67 | else: 68 | print("Unknown argument") 69 | elif argv[1] == "submit": 70 | if len(argv) < 3: 71 | print("################################\n" 72 | "# Building a multi-signature transaction with timelocked refund\n" 73 | "#\n" 74 | "# Requirements:\n" 75 | "# - A tx_wrapper json file\n" 76 | "#\n" 77 | "# Usage:\n" 78 | "# - Run './py example.py submit ' to submit a transaction\n" 79 | "################################") 80 | return 81 | if len(argv) < 4: 82 | node = "http://127.0.0.1:13413" 83 | print("Missing node URL, assuming '{}'".format(node)) 84 | else: 85 | node = argv[3] 86 | submit.submit(argv[2], node) 87 | elif argv[1] == "swap": 88 | if len(argv) < 3: 89 | print("################################\n" 90 | "# Atomic swap\n" 91 | "#\n" 92 | "# Run './swap sell' to sell grin for ETH\n" 93 | "# Run './swap buy' to buy grin for ETH\n" 94 | "################################") 95 | return 96 | 97 | if argv[2] == "sell": 98 | if len(argv) >= 4: 99 | swap.sell(argv[3]) 100 | else: 101 | swap.sell() 102 | elif argv[2] == "buy": 103 | if len(argv) >= 4: 104 | swap.buy(argv[3]) 105 | else: 106 | swap.buy() 107 | else: 108 | print("Unknown argument") 109 | elif argv[1] == "wallet": 110 | wallet.outputs() 111 | else: 112 | print("Unknown example") 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /tests/test_bulletproof.py: -------------------------------------------------------------------------------- 1 | import json 2 | from random import randint 3 | from secp256k1 import FLAG_ALL 4 | from secp256k1.key import SecretKey 5 | from secp256k1.pedersen import Secp256k1, RangeProof 6 | from grin.keychain import ChildKey, Identifier 7 | from grin.proof import MultiPartyBulletProof, TwoPartyBulletProof 8 | 9 | 10 | def test_proofs(create=False): 11 | secp = Secp256k1(None, FLAG_ALL) 12 | 13 | if create: 14 | dct = [] 15 | for i in range(100): 16 | secret = SecretKey.random(secp) 17 | nonce = SecretKey.random(secp) 18 | amount = randint(1, 10000000000) 19 | proof = secp.bullet_proof(amount, secret, nonce, bytearray()) 20 | dct.append({ 21 | "secret": secret.to_hex().decode(), 22 | "nonce": nonce.to_hex().decode(), 23 | "amount": amount, 24 | "proof": proof.to_hex().decode() 25 | }) 26 | f = open("proofs_new.json", "w") 27 | f.write(json.dumps(dct, indent=2)) 28 | f.close() 29 | return 30 | 31 | f = open("tests/proofs.json", "r") 32 | dct = json.loads(f.read()) 33 | f.close() 34 | 35 | test_count = 0 36 | test_equal_count = 0 37 | for test in dct: 38 | test_count += 1 39 | secret = SecretKey.from_hex(secp, test['secret'].encode()) 40 | nonce = SecretKey.from_hex(secp, test['nonce'].encode()) 41 | amount = test['amount'] 42 | proof_target = RangeProof.from_hex(test['proof'].encode()) 43 | proof = secp.bullet_proof(amount, secret, nonce, bytearray()) 44 | commit = secp.commit(amount, secret) 45 | assert secp.verify_bullet_proof(commit, proof, bytearray()), "Proof {} fails to verify".format(test_count) 46 | if proof == proof_target: 47 | test_equal_count += 1 48 | 49 | assert test_count == test_equal_count, \ 50 | "All proofs verify, but only {} out of {} proofs are the same".format(test_equal_count, test_count) 51 | 52 | 53 | def multi_party_proof(n): 54 | assert n > 1 55 | secp = Secp256k1(None, FLAG_ALL) 56 | mpps = [] 57 | child_keys = [] 58 | amount = randint(1, 999999) 59 | commit_sum = [secp.commit_value(amount)] 60 | common_nonce = SecretKey.random(secp) 61 | for i in range(n): 62 | child_keys.append(ChildKey(i, Identifier.random(), Identifier.random(), SecretKey.random(secp))) 63 | child_key = child_keys[i] 64 | commit_sum.append(secp.commit(0, child_key.key)) 65 | # Total commitment: sum xi*G + v*H 66 | commitment = secp.commit_sum(commit_sum, []) 67 | t_1s = [] 68 | t_2s = [] 69 | # Each party exports their T1 and T2 70 | for i in range(n): 71 | mpps.append(MultiPartyBulletProof(secp, child_keys[i], amount, commitment, common_nonce)) 72 | t_1, t_2 = mpps[i].round_1() 73 | t_1s.append(t_1) 74 | t_2s.append(t_2) 75 | # Each party receives the other T1 and T2 values and adds it to their own 76 | for i in range(n): 77 | for j in range(n): 78 | if i != j: 79 | mpps[i].fill_round_1(t_1s[j], t_2s[j]) 80 | tau_xs = [] 81 | # Each party exports their tau_x 82 | for i in range(n): 83 | tau_xs.append(mpps[i].round_2()) 84 | # One party receives the other tau_x values and adds it to their own 85 | # In this test we simulate each party calculating the proof 86 | for i in range(n): 87 | for j in range(n): 88 | if i != j: 89 | mpps[i].fill_round_2(tau_xs[j]) 90 | proofs = [] 91 | for i in range(n): 92 | proofs.append(mpps[i].finalize()) 93 | assert secp.verify_bullet_proof(commitment, proofs[i], bytearray()) 94 | for i in range(n-1): 95 | assert proofs[i+1] == proofs[0] 96 | 97 | 98 | def test_multi_party_proofs(): 99 | for i in range(2, 9): 100 | multi_party_proof(i) 101 | 102 | 103 | def test_two_party_proof(): 104 | secp = Secp256k1(None, FLAG_ALL) 105 | secret_key_a = SecretKey.random(secp) 106 | public_key_a = secret_key_a.to_public_key(secp) 107 | child_key_a = ChildKey(0, Identifier.random(), Identifier.random(), secret_key_a) 108 | secret_key_b = SecretKey.random(secp) 109 | public_key_b = secret_key_b.to_public_key(secp) 110 | child_key_b = ChildKey(0, Identifier.random(), Identifier.random(), secret_key_b) 111 | amount = randint(1, 999999) 112 | commit = secp.commit_sum([secp.commit(amount, secret_key_a), secp.commit(0, secret_key_b)], []) 113 | tpp_a = TwoPartyBulletProof(secp, child_key_a, public_key_b, amount, commit) 114 | tpp_b = TwoPartyBulletProof(secp, child_key_b, public_key_a, amount, commit) 115 | t_1_a, t_2_a = tpp_a.round_1() 116 | t_1_b, t_2_b = tpp_b.round_1() 117 | tpp_a.fill_round_1(t_1_b, t_2_b) 118 | tpp_b.fill_round_1(t_1_a, t_2_a) 119 | tau_x_a = tpp_a.round_2() 120 | tau_x_b = tpp_b.round_2() 121 | tpp_a.fill_round_2(tau_x_b) 122 | tpp_b.fill_round_2(tau_x_a) 123 | proof_a = tpp_a.finalize() 124 | assert secp.verify_bullet_proof(commit, proof_a, bytearray()) 125 | proof_b = tpp_b.finalize() 126 | assert proof_a == proof_b 127 | -------------------------------------------------------------------------------- /_cffi_build/secp256k1.h: -------------------------------------------------------------------------------- 1 | typedef struct secp256k1_context_struct secp256k1_context; 2 | 3 | typedef struct secp256k1_scratch_space_struct secp256k1_scratch_space; 4 | 5 | typedef struct { 6 | unsigned char data[64]; 7 | } secp256k1_pubkey; 8 | 9 | typedef struct { 10 | unsigned char data[64]; 11 | } secp256k1_ecdsa_signature; 12 | 13 | typedef int (*secp256k1_nonce_function)( 14 | unsigned char *nonce32, 15 | const unsigned char *msg32, 16 | const unsigned char *key32, 17 | const unsigned char *algo16, 18 | void *data, 19 | unsigned int attempt 20 | ); 21 | 22 | #define SECP256K1_FLAGS_TYPE_MASK ... 23 | #define SECP256K1_FLAGS_TYPE_CONTEXT ... 24 | #define SECP256K1_FLAGS_TYPE_COMPRESSION ... 25 | #define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY ... 26 | #define SECP256K1_FLAGS_BIT_CONTEXT_SIGN ... 27 | #define SECP256K1_FLAGS_BIT_COMPRESSION ... 28 | 29 | #define SECP256K1_CONTEXT_VERIFY ... 30 | #define SECP256K1_CONTEXT_SIGN ... 31 | #define SECP256K1_CONTEXT_NONE ... 32 | 33 | #define SECP256K1_EC_COMPRESSED ... 34 | #define SECP256K1_EC_UNCOMPRESSED ... 35 | 36 | #define SECP256K1_TAG_PUBKEY_EVEN ... 37 | #define SECP256K1_TAG_PUBKEY_ODD ... 38 | #define SECP256K1_TAG_PUBKEY_UNCOMPRESSED ... 39 | #define SECP256K1_TAG_PUBKEY_HYBRID_EVEN ... 40 | #define SECP256K1_TAG_PUBKEY_HYBRID_ODD ... 41 | 42 | secp256k1_context* secp256k1_context_create( 43 | unsigned int flags 44 | ); 45 | 46 | secp256k1_context* secp256k1_context_clone( 47 | const secp256k1_context* ctx 48 | ); 49 | 50 | void secp256k1_context_destroy( 51 | secp256k1_context* ctx 52 | ); 53 | 54 | void secp256k1_context_set_illegal_callback( 55 | secp256k1_context* ctx, 56 | void (*fun)(const char* message, void* data), 57 | const void* data 58 | ); 59 | 60 | void secp256k1_context_set_error_callback( 61 | secp256k1_context* ctx, 62 | void (*fun)(const char* message, void* data), 63 | const void* data 64 | ); 65 | 66 | secp256k1_scratch_space* secp256k1_scratch_space_create( 67 | const secp256k1_context* ctx, 68 | size_t max_size 69 | ); 70 | 71 | void secp256k1_scratch_space_destroy( 72 | secp256k1_scratch_space* scratch 73 | ); 74 | 75 | int secp256k1_ec_pubkey_parse( 76 | const secp256k1_context* ctx, 77 | secp256k1_pubkey* pubkey, 78 | const unsigned char *input, 79 | size_t inputlen 80 | ); 81 | 82 | int secp256k1_ec_pubkey_serialize( 83 | const secp256k1_context* ctx, 84 | unsigned char *output, 85 | size_t *outputlen, 86 | const secp256k1_pubkey* pubkey, 87 | unsigned int flags 88 | ); 89 | 90 | int secp256k1_ecdsa_signature_parse_compact( 91 | const secp256k1_context* ctx, 92 | secp256k1_ecdsa_signature* sig, 93 | const unsigned char *input64 94 | ); 95 | 96 | int secp256k1_ecdsa_signature_parse_der( 97 | const secp256k1_context* ctx, 98 | secp256k1_ecdsa_signature* sig, 99 | const unsigned char *input, 100 | size_t inputlen 101 | ); 102 | 103 | int secp256k1_ecdsa_signature_serialize_der( 104 | const secp256k1_context* ctx, 105 | unsigned char *output, 106 | size_t *outputlen, 107 | const secp256k1_ecdsa_signature* sig 108 | ); 109 | 110 | int secp256k1_ecdsa_signature_serialize_compact( 111 | const secp256k1_context* ctx, 112 | unsigned char *output64, 113 | const secp256k1_ecdsa_signature* sig 114 | ); 115 | 116 | int secp256k1_ecdsa_verify( 117 | const secp256k1_context* ctx, 118 | const secp256k1_ecdsa_signature *sig, 119 | const unsigned char *msg32, 120 | const secp256k1_pubkey *pubkey 121 | ); 122 | 123 | int secp256k1_ecdsa_signature_normalize( 124 | const secp256k1_context* ctx, 125 | secp256k1_ecdsa_signature *sigout, 126 | const secp256k1_ecdsa_signature *sigin 127 | ); 128 | 129 | extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979; 130 | 131 | extern const secp256k1_nonce_function secp256k1_nonce_function_default; 132 | 133 | int secp256k1_ecdsa_sign( 134 | const secp256k1_context* ctx, 135 | secp256k1_ecdsa_signature *sig, 136 | const unsigned char *msg32, 137 | const unsigned char *seckey, 138 | secp256k1_nonce_function noncefp, 139 | const void *ndata 140 | ); 141 | 142 | int secp256k1_ec_seckey_verify( 143 | const secp256k1_context* ctx, 144 | const unsigned char *seckey 145 | ); 146 | 147 | int secp256k1_ec_pubkey_create( 148 | const secp256k1_context* ctx, 149 | secp256k1_pubkey *pubkey, 150 | const unsigned char *seckey 151 | ); 152 | 153 | int secp256k1_ec_privkey_negate( 154 | const secp256k1_context* ctx, 155 | unsigned char *seckey 156 | ); 157 | 158 | int secp256k1_ec_pubkey_negate( 159 | const secp256k1_context* ctx, 160 | secp256k1_pubkey *pubkey 161 | ); 162 | 163 | int secp256k1_ec_privkey_tweak_add( 164 | const secp256k1_context* ctx, 165 | unsigned char *seckey, 166 | const unsigned char *tweak 167 | ); 168 | 169 | int secp256k1_ec_pubkey_tweak_add( 170 | const secp256k1_context* ctx, 171 | secp256k1_pubkey *pubkey, 172 | const unsigned char *tweak 173 | ); 174 | 175 | int secp256k1_ec_privkey_tweak_mul( 176 | const secp256k1_context* ctx, 177 | unsigned char *seckey, 178 | const unsigned char *tweak 179 | ); 180 | 181 | int secp256k1_ec_pubkey_tweak_mul( 182 | const secp256k1_context* ctx, 183 | secp256k1_pubkey *pubkey, 184 | const unsigned char *tweak 185 | ); 186 | 187 | int secp256k1_context_randomize( 188 | secp256k1_context* ctx, 189 | const unsigned char *seed32 190 | ); 191 | 192 | int secp256k1_ec_pubkey_combine( 193 | const secp256k1_context* ctx, 194 | secp256k1_pubkey *out, 195 | const secp256k1_pubkey * const * ins, 196 | size_t n 197 | ); 198 | -------------------------------------------------------------------------------- /examples/simple_tx.py: -------------------------------------------------------------------------------- 1 | import json 2 | from urllib.request import urlopen 3 | from http.server import HTTPServer 4 | from secp256k1 import FLAG_ALL 5 | from secp256k1.pedersen import Secp256k1 6 | from secp256k1.key import SecretKey 7 | from grin.transaction import tx_fee 8 | from grin.wallet import Wallet 9 | from grin.keychain import BlindSum 10 | from grin.slate import Slate, ParticipantData 11 | from grin.util import MILLI_GRIN_UNIT, GRIN_UNIT, HTTPServerHandler, set_callback_post 12 | 13 | secp = None 14 | wallet = None 15 | server = None 16 | 17 | 18 | def send(node_url: str): 19 | global secp, wallet 20 | send_amount = 1 21 | height = 33333 22 | lock_height = 33333 23 | features = 0 24 | dest_url = "http://127.0.0.1:17175" 25 | fluff = True 26 | n_outputs = 2 27 | 28 | secp = Secp256k1(None, FLAG_ALL) 29 | 30 | wallet = Wallet.open(secp, "wallet_1") 31 | 32 | print("Preparing to send {} grin to {}".format(send_amount / GRIN_UNIT, dest_url)) 33 | 34 | input_entries = wallet.select_outputs(send_amount + tx_fee(1, n_outputs, MILLI_GRIN_UNIT)) 35 | fee_amount = tx_fee(len(input_entries), n_outputs, MILLI_GRIN_UNIT) 36 | input_amount = sum(x.value for x in input_entries) 37 | change_amount = input_amount - send_amount - fee_amount 38 | 39 | print("Selected {} inputs".format(len(input_entries))) 40 | 41 | blind_sum = BlindSum() 42 | 43 | slate = Slate.blank(secp, 2, send_amount, height, features, fee_amount, lock_height) 44 | 45 | # Inputs 46 | for entry in input_entries: 47 | entry.mark_locked() 48 | blind_sum.sub_child_key(wallet.derive_from_entry(entry)) 49 | slate.tx.add_input(secp, wallet.entry_to_input(entry)) 50 | 51 | # Change output 52 | change_key, change_entry = wallet.create_output(change_amount) 53 | blind_sum.add_child_key(change_key) 54 | slate.tx.add_output(secp, wallet.entry_to_output(change_entry)) 55 | 56 | # Excess 57 | blind_sum.sub_blinding_factor(slate.tx.offset) 58 | excess = wallet.chain.blind_sum(blind_sum).to_secret_key(secp) 59 | public_excess = excess.to_public_key(secp) 60 | 61 | # Nonce 62 | nonce = SecretKey.random(secp) 63 | public_nonce = nonce.to_public_key(secp) 64 | 65 | # Participant 66 | participant = ParticipantData(0, public_excess, public_nonce, None) 67 | slate.add_participant(participant) 68 | 69 | print("Sending slate to receiver..") 70 | 71 | req = urlopen(dest_url, json.dumps(slate.to_dict(secp)).encode(), 600) 72 | slate = Slate.from_dict(secp, json.loads(req.read().decode())) 73 | 74 | print("Received response, finishing transaction..") 75 | 76 | participant = slate.get_participant(0) 77 | slate.partial_signature(secp, participant, excess, nonce) 78 | slate.finalize(secp) 79 | 80 | tx_wrapper = { 81 | "tx_hex": slate.tx.to_hex(secp).decode() 82 | } 83 | 84 | print("Submitting to node..") 85 | 86 | urlopen("{}/v1/pool/push".format(node_url) + ("?fluff" if fluff else ""), json.dumps(tx_wrapper).encode(), 600) 87 | 88 | wallet.save() 89 | 90 | print("Transaction complete!") 91 | 92 | 93 | def post(handler: HTTPServerHandler): 94 | global secp, wallet, server 95 | try: 96 | length = handler.headers['Content-Length'] 97 | length = 0 if length is None else int(length) 98 | raw = "" 99 | if length > 0: 100 | raw = handler.rfile.read(length).decode() 101 | f = open("simple_tx_receive.json", "w") 102 | f.write(raw) 103 | f.close() 104 | slate = Slate.from_dict(secp, json.loads(raw)) 105 | 106 | print("Receive {} grin".format(slate.amount / GRIN_UNIT)) 107 | 108 | # Output 109 | # n_outputs = min(100, slate.amount) 110 | n_outputs = 1 111 | blind_sum = BlindSum() 112 | output_child_key, output_entry = wallet.create_output(slate.amount-n_outputs+1) 113 | print("Generate output 0: {}".format(wallet.commit(output_entry))) 114 | print() 115 | blind_sum.add_child_key(output_child_key) 116 | slate.tx.add_output(secp, wallet.entry_to_output(output_entry)) 117 | if n_outputs > 1: 118 | for i in range(n_outputs-1): 119 | output_child_key_loop, output_entry_loop = wallet.create_output(1) 120 | print("Generate output {}: {}".format(i + 1, wallet.commit(output_entry_loop))) 121 | blind_sum.add_child_key(output_child_key_loop) 122 | slate.tx.add_output(secp, wallet.entry_to_output(output_entry_loop)) 123 | 124 | # Excess 125 | excess = wallet.chain.blind_sum(blind_sum).to_secret_key(secp) 126 | public_excess = excess.to_public_key(secp) 127 | print("Generated excess") 128 | 129 | # Nonce 130 | nonce = SecretKey.random(secp) 131 | public_nonce = nonce.to_public_key(secp) 132 | 133 | # Add participant data 134 | participant = ParticipantData(1, public_excess, public_nonce, None) 135 | slate.add_participant(participant) 136 | 137 | # After all participants published their nonce, calculate partial signature 138 | slate.partial_signature(secp, participant, excess, nonce) 139 | 140 | resp = json.dumps(slate.to_dict(secp)) 141 | 142 | wallet.save() 143 | 144 | handler.json_response((resp + "\r\n").encode()) 145 | 146 | print("Sent response") 147 | except Exception as e: 148 | print("Unable to parse slate: {}".format(e)) 149 | handler.error_response() 150 | 151 | 152 | def receive(): 153 | global secp, wallet, server 154 | secp = Secp256k1(None, FLAG_ALL) 155 | wallet = Wallet.open(secp, "wallet") 156 | 157 | set_callback_post(post) 158 | 159 | print("Listening on port 17175..") 160 | server = HTTPServer(("", 17175), HTTPServerHandler) 161 | try: 162 | server.serve_forever() 163 | except KeyboardInterrupt: 164 | server.socket.close() 165 | 166 | -------------------------------------------------------------------------------- /grin/slate.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from grin.transaction import Transaction 3 | from grin.util import UUID 4 | import grin.aggsig as aggsig 5 | from secp256k1.pedersen import Secp256k1 6 | from secp256k1.key import SecretKey, PublicKey 7 | from secp256k1.aggsig import Signature 8 | 9 | 10 | class ParticipantData: 11 | def __init__(self, id: int, public_blind_excess: PublicKey, public_nonce: PublicKey, 12 | partial_signature: Optional[Signature]): 13 | self.id = id 14 | self.public_blind_excess = public_blind_excess 15 | self.public_nonce = public_nonce 16 | self.partial_signature = partial_signature 17 | 18 | def to_dict(self, secp: Secp256k1, short=False) -> dict: 19 | return { 20 | "id": self.id, 21 | "public_blind_excess": self.public_blind_excess.to_hex(secp).decode() if short else 22 | list(self.public_blind_excess.to_bytearray(secp)), 23 | "public_nonce": self.public_nonce.to_hex(secp).decode() if short else 24 | list(self.public_nonce.to_bytearray(secp)), 25 | "part_sig": None if self.partial_signature is None else ( 26 | self.partial_signature.to_hex().decode() if short else 27 | list(self.partial_signature.to_bytearray(secp, True)) 28 | ) 29 | } 30 | 31 | def is_complete(self) -> bool: 32 | return self.partial_signature is not None 33 | 34 | @staticmethod 35 | def from_dict(secp: Secp256k1, dct: dict): 36 | sig = dct['part_sig'] 37 | return ParticipantData( 38 | dct['id'], 39 | PublicKey.from_bytearray(secp, bytearray(dct['public_blind_excess'])), 40 | PublicKey.from_bytearray(secp, bytearray(dct['public_nonce'])), 41 | None if sig is None else Signature.from_bytearray(secp, bytearray(sig), True) 42 | ) 43 | 44 | 45 | class InvalidSignatureException(Exception): 46 | pass 47 | 48 | 49 | class Slate: 50 | def __init__(self, num_participants: int, id: UUID, tx: Transaction, amount: int, fee: int, height: int, 51 | lock_height: int, participant_data: List[ParticipantData]): 52 | self.num_participants = num_participants 53 | self.id = id 54 | self.tx = tx 55 | self.amount = amount 56 | self.fee = fee 57 | self.height = height 58 | self.lock_height = lock_height 59 | self.participant_data = participant_data 60 | 61 | def to_dict(self, secp: Secp256k1, short=False) -> dict: 62 | return { 63 | "num_participants": self.num_participants, 64 | "id": str(self.id), 65 | "tx": self.tx.to_dict(secp, short), 66 | "amount": self.amount, 67 | "fee": self.fee, 68 | "height": self.height, 69 | "lock_height": self.lock_height, 70 | "participant_data": [x.to_dict(secp, short) for x in self.participant_data] 71 | } 72 | 73 | def add_participant(self, participant_data: ParticipantData): 74 | self.participant_data.append(participant_data) 75 | 76 | def get_participant(self, id: int) -> Optional[ParticipantData]: 77 | for participant in self.participant_data: 78 | if participant.id == id: 79 | return participant 80 | return None 81 | 82 | def public_blind_excess_sum(self, secp: Secp256k1) -> PublicKey: 83 | return PublicKey.from_combination(secp, [x.public_blind_excess for x in self.participant_data]) 84 | 85 | def public_nonce_sum(self, secp: Secp256k1) -> PublicKey: 86 | return PublicKey.from_combination(secp, [x.public_nonce for x in self.participant_data]) 87 | 88 | def partial_signature(self, secp: Secp256k1, participant: ParticipantData, secret_key: SecretKey, 89 | secret_nonce: SecretKey): 90 | participant.partial_signature = aggsig.calculate_partial( 91 | secp, secret_key, secret_nonce, self.public_blind_excess_sum(secp), self.public_nonce_sum(secp), 92 | self.fee, self.lock_height 93 | ) 94 | 95 | def verify_partial_signatures(self, secp: Secp256k1): 96 | for participant in self.participant_data: 97 | if participant.is_complete(): 98 | res = aggsig.verify_partial( 99 | secp, 100 | participant.partial_signature, 101 | self.public_nonce_sum(secp), 102 | participant.public_blind_excess, 103 | self.fee, 104 | self.lock_height 105 | ) 106 | if not res: 107 | raise InvalidSignatureException() 108 | 109 | def finalize(self, secp: Secp256k1): 110 | signature = self.finalize_signature(secp) 111 | self.finalize_transaction(secp, signature) 112 | 113 | def finalize_signature(self, secp: Secp256k1) -> Signature: 114 | self.verify_partial_signatures(secp) 115 | partial_signatures = [x.partial_signature for x in self.participant_data] 116 | public_nonce_sum = self.public_nonce_sum(secp) 117 | public_key_sum = self.public_blind_excess_sum(secp) 118 | signature = aggsig.add_partials(secp, partial_signatures, public_nonce_sum) 119 | if not aggsig.verify(secp, signature, public_key_sum, self.fee, self.lock_height): 120 | raise InvalidSignatureException() 121 | 122 | return signature 123 | 124 | def finalize_transaction(self, secp: Secp256k1, signature: Signature): 125 | excess = self.tx.sum_commitments(secp) 126 | self.tx.kernels[0].excess = excess 127 | self.tx.kernels[0].excess_signature = signature 128 | if not self.tx.kernels[0].verify(secp): 129 | raise InvalidSignatureException() 130 | # TODO: verify all parts of tx 131 | 132 | @staticmethod 133 | def blank(secp: Secp256k1, num_participants: int, amount: int, height: int, features: int, 134 | fee: int, lock_height: int): 135 | slate = Slate( 136 | num_participants, UUID.random(), Transaction.empty(secp, features, fee, lock_height), amount, 137 | fee, height, lock_height, [] 138 | ) 139 | return slate 140 | 141 | @staticmethod 142 | def from_dict(secp: Secp256k1, dct: dict): 143 | return Slate( 144 | dct['num_participants'], 145 | UUID.from_str(dct['id']), 146 | Transaction.from_dict(secp, dct['tx']), 147 | dct['amount'], 148 | dct['fee'], 149 | dct['height'], 150 | dct['lock_height'], 151 | [ParticipantData.from_dict(secp, x) for x in dct['participant_data']] 152 | ) 153 | -------------------------------------------------------------------------------- /tests/test_key.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from secp256k1 import Secp256k1, FLAG_ALL, SECRET_KEY_SIZE 3 | from secp256k1.key import SecretKey, PublicKey 4 | 5 | 6 | def test_secret_key(): 7 | secp = Secp256k1(None, FLAG_ALL) 8 | 9 | # (de)serialization 10 | key_a = SecretKey.random(secp) 11 | key_b = SecretKey.from_bytearray(secp, key_a.to_bytearray()) 12 | assert key_a == key_b 13 | 14 | # Too short 15 | with raises(Exception): 16 | SecretKey.from_bytearray(secp, bytearray([0x01] * (SECRET_KEY_SIZE-1))) 17 | 18 | # Too long 19 | with raises(Exception): 20 | SecretKey.from_bytearray(secp, bytearray([0x01] * (SECRET_KEY_SIZE+1))) 21 | 22 | # Zero 23 | with raises(Exception): 24 | SecretKey.from_bytearray(secp, bytearray([0] * SECRET_KEY_SIZE)) 25 | 26 | # Overflow 27 | with raises(Exception): 28 | SecretKey.from_bytearray(secp, bytearray([0xFF] * SECRET_KEY_SIZE)) 29 | 30 | # Top of range 31 | SecretKey.from_bytearray(secp, bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 32 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 33 | 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 34 | 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x40])) 35 | 36 | # One past top of range 37 | with raises(Exception): 38 | SecretKey.from_bytearray(secp, bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 40 | 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 41 | 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41])) 42 | 43 | # a=a 44 | key_a = SecretKey.from_bytearray(secp, bytearray([0xCB] * SECRET_KEY_SIZE)) 45 | key_b = SecretKey.from_bytearray(secp, bytearray([0xCB] * SECRET_KEY_SIZE)) 46 | assert key_a == key_b 47 | 48 | # a!=b 49 | key_b = SecretKey.from_bytearray(secp, bytearray([0xCC] * SECRET_KEY_SIZE)) 50 | assert key_a != key_b 51 | 52 | # a+b 53 | key_a = SecretKey.from_bytearray(secp, bytearray([0xDD] * SECRET_KEY_SIZE)) 54 | key_b = SecretKey.from_bytearray(secp, bytearray([0x02] * SECRET_KEY_SIZE)) 55 | key_a.add_assign(secp, key_b) 56 | key_c = SecretKey.from_bytearray(secp, bytearray([0xDF] * SECRET_KEY_SIZE)) 57 | assert key_a == key_c 58 | 59 | # a+b = b+a 60 | key_a = SecretKey.random(secp) 61 | key_b = SecretKey.random(secp) 62 | key_a_b = key_a.add(secp, key_b) 63 | key_b_a = key_b.add(secp, key_a) 64 | assert key_a_b == key_b_a 65 | 66 | # Key addition where sum > order (N-1+N-2=N-3) 67 | key_a = SecretKey.from_bytearray(secp, bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 68 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 69 | 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 70 | 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x40])) 71 | key_b = SecretKey.from_bytearray(secp, bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 72 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 73 | 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 74 | 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x3F])) 75 | key_a.add_assign(secp, key_b) 76 | key_c = SecretKey.from_bytearray(secp, bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 77 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 78 | 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 79 | 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x3E])) 80 | assert key_a == key_c 81 | 82 | # ab = ba 83 | key_a = SecretKey.random(secp) 84 | key_b = SecretKey.random(secp) 85 | key_ab = key_a.mul(secp, key_b) 86 | key_ba = key_b.mul(secp, key_a) 87 | assert key_ab == key_ba 88 | 89 | # c(a+b) = ca + cb 90 | key_a = SecretKey.random(secp) 91 | key_b = SecretKey.random(secp) 92 | key_c = SecretKey.random(secp) 93 | key_ca = key_a.mul(secp, key_c) 94 | key_cb = key_b.mul(secp, key_c) 95 | key_ca_b = key_a.add(secp, key_b) 96 | key_ca_b.mul_assign(secp, key_c) 97 | key_ca_cb = key_ca.add(secp, key_cb) 98 | assert key_ca_b == key_ca_cb 99 | 100 | 101 | def test_public_key(): 102 | secp = Secp256k1(None, FLAG_ALL) 103 | 104 | # (de)serialization 105 | secret_key = SecretKey.random(secp) 106 | public_key = secret_key.to_public_key(secp) 107 | public_key_2 = PublicKey.from_bytearray(secp, public_key.to_bytearray(secp)) 108 | assert public_key == public_key_2 109 | 110 | # (a+b)*G = a*G + b*G 111 | secret_key_a = SecretKey.random(secp) 112 | secret_key_b = SecretKey.random(secp) 113 | secret_key_a_b = secret_key_a.add(secp, secret_key_b) 114 | public_key_a = secret_key_a.to_public_key(secp) 115 | public_key_b = secret_key_b.to_public_key(secp) 116 | public_key_a_b = secret_key_a_b.to_public_key(secp) 117 | public_key_a_b_2 = PublicKey.from_combination(secp, [public_key_a, public_key_b]) 118 | public_key_a_b_3 = public_key_a.add_scalar(secp, secret_key_b) 119 | assert public_key_a_b == public_key_a_b_2 120 | assert public_key_a_b == public_key_a_b_3 121 | 122 | # (ab)*G = a(b*G) = b(a*G) 123 | secret_key_a = SecretKey.random(secp) 124 | secret_key_b = SecretKey.random(secp) 125 | secret_key_ab = secret_key_a.mul(secp, secret_key_b) 126 | public_key_ab = secret_key_ab.to_public_key(secp) 127 | public_key_ab_2 = secret_key_a.to_public_key(secp) 128 | public_key_ab_2.mul_assign(secp, secret_key_b) 129 | public_key_ab_3 = secret_key_b.to_public_key(secp) 130 | public_key_ab_3.mul_assign(secp, secret_key_a) 131 | assert public_key_ab == public_key_ab_2 132 | assert public_key_ab == public_key_ab_3 133 | 134 | # (c(a+b))*G = c(a*G) + c(b*G) 135 | secret_key_a = SecretKey.random(secp) 136 | secret_key_b = SecretKey.random(secp) 137 | secret_key_c = SecretKey.random(secp) 138 | secret_key_ca_b = secret_key_a.add(secp, secret_key_b) 139 | secret_key_ca_b.mul_assign(secp, secret_key_c) 140 | public_key_ca_b = secret_key_ca_b.to_public_key(secp) 141 | public_key_ca = secret_key_a.to_public_key(secp) 142 | public_key_ca.mul_assign(secp, secret_key_c) 143 | public_key_cb = secret_key_b.to_public_key(secp) 144 | public_key_cb.mul_assign(secp, secret_key_c) 145 | public_key_ca_cb = public_key_ca.add(secp, public_key_cb) 146 | assert public_key_ca_b == public_key_ca_cb 147 | 148 | # (a+b+c)*G = a*G + b*G + c*G 149 | secret_key_a = SecretKey.random(secp) 150 | secret_key_b = SecretKey.random(secp) 151 | secret_key_c = SecretKey.random(secp) 152 | secret_key_a_b_c = secret_key_a.add(secp, secret_key_b) 153 | secret_key_a_b_c.add_assign(secp, secret_key_c) 154 | public_key_a_b_c = secret_key_a_b_c.to_public_key(secp) 155 | public_key_a = secret_key_a.to_public_key(secp) 156 | public_key_b = secret_key_b.to_public_key(secp) 157 | public_key_c = secret_key_c.to_public_key(secp) 158 | public_key_a_b_c_2 = PublicKey.from_combination(secp, [public_key_a, public_key_b, public_key_c]) 159 | assert public_key_a_b_c == public_key_a_b_c_2 160 | 161 | -------------------------------------------------------------------------------- /grin/util.py: -------------------------------------------------------------------------------- 1 | from binascii import unhexlify, hexlify 2 | from enum import Enum 3 | from hashlib import blake2b, sha256, new as hashlibnew 4 | from http.server import BaseHTTPRequestHandler 5 | import os 6 | 7 | MILLI_GRIN_UNIT = 1000000 8 | GRIN_UNIT = 1000000000 9 | 10 | 11 | def absolute(*paths): 12 | op = os.path 13 | return op.realpath(op.abspath(op.join(op.dirname(__file__), "..", *paths))) 14 | 15 | 16 | def kernel_sig_msg(fee: int, lock_height: int) -> bytearray: 17 | out = bytearray([0] * 32) 18 | out[16:24] = bytearray(fee.to_bytes(8, "big")) 19 | out[24:32] = bytearray(lock_height.to_bytes(8, "big")) 20 | return out 21 | 22 | 23 | def hasher(data: bytearray) -> bytes: 24 | return blake2b(bytes(data), digest_size=32).digest() 25 | 26 | 27 | def sort_by_hash(collection: list, secp=None) -> list: 28 | if secp is None: 29 | return sorted(collection, key=lambda x: x.hash()) 30 | return sorted(collection, key=lambda x: x.hash(secp)) 31 | 32 | 33 | class UUID: 34 | def __init__(self, data: bytearray): 35 | assert len(data) == 16, "Invalid UUID size" 36 | self.uuid = data 37 | 38 | def __str__(self): 39 | hex = hexlify(self.uuid) 40 | return "{}-{}-{}-{}-{}".format(hex[0:8].decode(), hex[8:12].decode(), hex[12:16].decode(), 41 | hex[16:20].decode(), hex[20:32].decode()) 42 | 43 | @staticmethod 44 | def random(): 45 | return UUID(bytearray(os.urandom(16))) 46 | 47 | @staticmethod 48 | def from_str(hyphenated: str): 49 | parts = hyphenated.split("-") 50 | data = bytearray() 51 | for part in parts: 52 | data.extend(part.encode()) 53 | return UUID(bytearray(unhexlify(data))) 54 | 55 | 56 | class OrderedEnum(Enum): 57 | def __ge__(self, other): 58 | if self.__class__ is other.__class__: 59 | return self.value >= other.value 60 | return NotImplemented 61 | 62 | def __gt__(self, other): 63 | if self.__class__ is other.__class__: 64 | return self.value > other.value 65 | return NotImplemented 66 | 67 | def __le__(self, other): 68 | if self.__class__ is other.__class__: 69 | return self.value <= other.value 70 | return NotImplemented 71 | 72 | def __lt__(self, other): 73 | if self.__class__ is other.__class__: 74 | return self.value < other.value 75 | return NotImplemented 76 | 77 | 78 | def do_nothing(handler): 79 | handler.error_response() 80 | 81 | http_callback_get = do_nothing 82 | http_callback_post = do_nothing 83 | 84 | 85 | def set_callback_get(name): 86 | global http_callback_get 87 | http_callback_get = name 88 | 89 | 90 | def set_callback_post(name): 91 | global http_callback_post 92 | http_callback_post = name 93 | 94 | 95 | class HTTPServerHandler(BaseHTTPRequestHandler): 96 | def __init__(self, request, client_address, server): 97 | self.responded = False 98 | super().__init__(request, client_address, server) 99 | 100 | def send_response(self, code, message=None): 101 | if not self.responded: 102 | self.responded = True 103 | super().send_response(code, message) 104 | 105 | def json_response(self, data): 106 | if self.responded: 107 | return 108 | self.send_response(200) 109 | self.send_header("Content-type", "application/json") 110 | self.end_headers() 111 | self.wfile.write(data) 112 | 113 | def response(self, data): 114 | if self.responded: 115 | return 116 | self.send_response(200) 117 | self.send_header("Content-type", "text/html") 118 | self.end_headers() 119 | self.wfile.write(data) 120 | 121 | def error_response(self): 122 | if self.responded: 123 | return 124 | self.send_response(500) 125 | self.send_header("Content-type", "text/html") 126 | self.end_headers() 127 | self.wfile.write(b"Error") 128 | 129 | def do_GET(self): 130 | http_callback_get(self) 131 | 132 | def do_POST(self): 133 | http_callback_post(self) 134 | 135 | 136 | def hash160(data: bytearray) -> bytearray: 137 | h = hashlibnew("ripemd160") 138 | h.update(sha256(bytes(data)).digest()) 139 | return bytearray(h.digest()) 140 | 141 | 142 | def hash256(data: bytearray) -> bytearray: 143 | return bytearray(sha256(sha256(bytes(data)).digest()).digest()) 144 | 145 | 146 | def base58check_encode(data: bytearray) -> bytes: 147 | checksum = hash256(data) 148 | data = data[:] 149 | data.extend(checksum[:4]) 150 | return base58_encode(data) 151 | 152 | BASE58_CHARS = bytearray(b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") 153 | 154 | 155 | def base58_encode(data: bytearray) -> bytes: 156 | leading_zero_count = 0 157 | leading_zeroes = True 158 | ret = bytearray() 159 | 160 | for d in data: 161 | carry = d 162 | if leading_zeroes and carry == 0: 163 | leading_zero_count += 1 164 | else: 165 | leading_zeroes = False 166 | 167 | for i in range(len(ret)): 168 | c = ret[i]*256 + carry 169 | ret[i] = c % 58 170 | carry = int(c / 58) 171 | 172 | while carry > 0: 173 | ret.append(carry % 58) 174 | carry = int(carry/58) 175 | 176 | for i in range(leading_zero_count): 177 | ret.append(0) 178 | ret.reverse() 179 | 180 | for i in range(len(ret)): 181 | ret[i] = BASE58_CHARS[ret[i]] 182 | 183 | return bytes(ret) 184 | 185 | 186 | def base58check_decode(data: bytes) -> bytearray: 187 | res = base58_decode(data) 188 | assert len(res) >= 4 189 | checksum_start = len(res) - 4 190 | hash = hash256(res[:checksum_start]) 191 | expected = hash[:4] 192 | actual = res[checksum_start:] 193 | assert expected == actual 194 | return res[:checksum_start] 195 | 196 | BASE58_DIGITS = [ 197 | None, None, None, None, None, None, None, None, 198 | None, None, None, None, None, None, None, None, 199 | None, None, None, None, None, None, None, None, 200 | None, None, None, None, None, None, None, None, 201 | None, None, None, None, None, None, None, None, 202 | None, None, None, None, None, None, None, None, 203 | None, 0, 1, 2, 3, 4, 5, 6, 204 | 7, 8, None, None, None, None, None, None, 205 | None, 9, 10, 11, 12, 13, 14, 15, 206 | 16, None, 17, 18, 19, 20, 21, None, 207 | 22, 23, 24, 25, 26, 27, 28, 29, 208 | 30, 31, 32, None, None, None, None, None, 209 | None, 33, 34, 35, 36, 37, 38, 39, 210 | 40, 41, 42, 43, None, 44, 45, 46, 211 | 47, 48, 49, 50, 51, 52, 53, 54, 212 | 55, 56, 57, None, None, None, None, None 213 | ] 214 | 215 | 216 | def base58_decode(data: bytes) -> bytearray: 217 | size = int(1 + len(data) * 11 / 15) 218 | scratch = bytearray([0] * size) 219 | 220 | for d in bytearray(data): 221 | assert d < 128 222 | carry = BASE58_DIGITS[d] 223 | assert carry is not None 224 | for i in range(len(scratch)): 225 | carry += scratch[size-i-1]*58 226 | scratch[size-i-1] = carry.to_bytes(4, "little")[0] 227 | carry = int(carry/256) 228 | assert carry == 0 229 | 230 | res = bytearray() 231 | for d in bytearray(data): 232 | if d != BASE58_CHARS[0]: 233 | break 234 | res.append(0) 235 | skipping = True 236 | for d in scratch: 237 | if skipping: 238 | if d == 0: 239 | continue 240 | else: 241 | skipping = False 242 | res.append(d) 243 | return res 244 | 245 | 246 | def var_int_encode(value: int) -> bytearray: 247 | data = bytearray() 248 | if value < 0xFD: 249 | data.extend(value.to_bytes(1, "little")) 250 | elif value <= 0xFFFF: 251 | data.append(0xFD) 252 | data.extend(value.to_bytes(2, "little")) 253 | elif value <= 0xFFFFFFFF: 254 | data.append(0xFE) 255 | data.extend(value.to_bytes(4, "little")) 256 | else: 257 | data.append(0xFF) 258 | data.extend(value.to_bytes(8, "little")) 259 | return data 260 | 261 | 262 | def script_write_bytes(value: int) -> bytearray: 263 | data = bytearray() 264 | if value <= 0x4B: 265 | data.extend(value.to_bytes(1, "little")) 266 | elif value <= 0xFF: 267 | data.append(0x4C) 268 | data.extend(value.to_bytes(1, "little")) 269 | elif value <= 0xFFFF: 270 | data.append(0x4D) 271 | data.extend(value.to_bytes(2, "little")) 272 | else: 273 | data.append(0x4E) 274 | data.extend(value.to_bytes(4, "little")) 275 | return data 276 | -------------------------------------------------------------------------------- /grin/wallet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from binascii import hexlify, unhexlify 4 | from typing import List 5 | from enum import Enum 6 | from grin.keychain import Keychain, Identifier, ChildKey, KeychainPath 7 | from grin.util import absolute 8 | from grin.transaction import Input, Output, OutputFeatures 9 | from secp256k1.pedersen import Secp256k1, Commitment 10 | 11 | 12 | class OutputStatus(Enum): 13 | Unconfirmed = 1, 14 | Unspent = 2, 15 | Locked = 3, 16 | Spent = 4 17 | 18 | 19 | class OutputEntry: 20 | def __init__(self, root_key_id: Identifier, key_id: Identifier, n_child: int, value: int, status: OutputStatus, 21 | height: int, lock_height: int, is_coinbase: bool): 22 | self.root_key_id = root_key_id 23 | self.key_id = key_id 24 | self.n_child = n_child 25 | self.value = value 26 | self.status = status 27 | self.height = height 28 | self.lock_height = lock_height 29 | self.is_coinbase = is_coinbase 30 | 31 | def __repr__(self): 32 | return "OutputEntry".format(self.n_child, self.value, self.status.name) 33 | 34 | def to_dict(self) -> dict: 35 | return { 36 | "root_key_id": self.root_key_id.to_hex().decode(), 37 | "key_id": self.key_id.to_hex().decode(), 38 | "n_child": self.n_child, 39 | "value": self.value, 40 | "status": self.status.name, 41 | "height": self.height, 42 | "lock_height": self.lock_height, 43 | "is_coinbase": self.is_coinbase 44 | } 45 | 46 | def update_from_dict(self, dct: dict): 47 | self.value = dct['value'] 48 | self.status = OutputStatus[dct['status']] 49 | self.height = dct['height'] 50 | self.lock_height = dct['lock_height'] 51 | self.is_coinbase = dct['is_coinbase'] 52 | 53 | def mark_locked(self): 54 | self.status = OutputStatus.Locked 55 | 56 | def mark_unspent(self): 57 | if self.status in (OutputStatus.Unconfirmed, OutputStatus.Locked): 58 | self.status = OutputStatus.Unspent 59 | 60 | def mark_spent(self): 61 | if self.status in (OutputStatus.Unspent, OutputStatus.Locked): 62 | self.status = OutputStatus.Spent 63 | 64 | @staticmethod 65 | def from_dict(dct: dict): 66 | root_key_id = Identifier.from_hex(dct['root_key_id'].encode()) 67 | key_id = Identifier.from_hex(dct['key_id'].encode()) 68 | status = OutputStatus[dct['status']] 69 | return OutputEntry(root_key_id, key_id, dct['n_child'], dct['value'], status, dct['height'], 70 | dct['lock_height'], dct['is_coinbase']) 71 | 72 | @staticmethod 73 | def from_child_key(child_key: ChildKey, value: int, is_coinbase: bool): 74 | return OutputEntry(child_key.root_key_id, child_key.key_id, child_key.n_child, value, 75 | OutputStatus.Unconfirmed, 0, 0, is_coinbase) 76 | 77 | 78 | class WalletDetails: 79 | def __init__(self, location: str): 80 | self.location = location 81 | self.last_confirmed_height = 0 82 | self.last_child_index = 0 83 | if not os.path.exists(location): 84 | self.save() 85 | else: 86 | self.load() 87 | 88 | def to_dict(self) -> dict: 89 | return { 90 | "last_confirmed_height": self.last_confirmed_height, 91 | "last_child_index": self.last_child_index 92 | } 93 | 94 | def load(self): 95 | f = open(self.location, "r") 96 | dct = json.loads(f.read()) 97 | f.close() 98 | self.last_confirmed_height = dct['last_confirmed_height'] 99 | self.last_child_index = dct['last_child_index'] 100 | 101 | def save(self): 102 | f = open(self.location, "w") 103 | f.write(json.dumps(self.to_dict(), indent=2)) 104 | f.close() 105 | 106 | def next(self) -> int: 107 | self.last_child_index += 1 108 | return self.last_child_index 109 | 110 | 111 | class NotEnoughFundsException(Exception): 112 | pass 113 | 114 | 115 | # File wallet 116 | # TODO: locking, output selection strategies 117 | class Wallet: 118 | def __init__(self, secp: Secp256k1, location: str, seed: bytes): 119 | self.dir_in = location 120 | self.dir = absolute(location) 121 | assert os.path.isdir(self.dir) 122 | self.chain = Keychain.from_seed(secp, seed) 123 | self.details = WalletDetails(absolute(self.dir, "wallet.det")) 124 | self.cache = {} 125 | self.outputs = {} 126 | if not os.path.exists(absolute(self.dir, "wallet.dat")): 127 | self.save() 128 | else: 129 | self.load() 130 | 131 | def load(self): 132 | self.details.load() 133 | f = open(absolute(self.dir, "wallet.dat"), "r") 134 | entries = json.loads(f.read()) 135 | f.close() 136 | outputs = {} 137 | for entry in entries: 138 | key_id = entry['key_id'] 139 | if key_id in self.outputs: 140 | output = self.outputs[key_id] 141 | output.update_from_dict(entry) 142 | outputs[key_id] = output 143 | outputs[key_id] = OutputEntry.from_dict(entry) 144 | self.outputs = outputs 145 | 146 | def save(self): 147 | self.details.save() 148 | lst = [y.to_dict() for x, y in self.outputs.items()] 149 | lst = sorted(lst, key=lambda x: x['n_child']) 150 | f = open(absolute(self.dir, "wallet.dat"), "w") 151 | f.write(json.dumps(lst, indent=2)) 152 | f.close() 153 | 154 | def get_output(self, key: str) -> OutputEntry: 155 | return self.outputs[key] if key in self.outputs else None 156 | 157 | def get_output_by_n(self, n: int) -> OutputEntry: 158 | return self.get_output(self.chain.derive(n).key_id.to_hex().decode()) 159 | 160 | def select_outputs(self, amount: int) -> List[OutputEntry]: 161 | total_amount = 0 162 | outputs = [] 163 | for n, output in self.outputs.items(): 164 | if output.status != OutputStatus.Unspent: 165 | continue 166 | total_amount += output.value 167 | outputs.append(output) 168 | if total_amount > amount: 169 | break 170 | if total_amount <= amount: 171 | raise NotEnoughFundsException() 172 | return outputs 173 | 174 | def create_output(self, value: int, is_coinbase=False) -> (ChildKey, OutputEntry): 175 | assert isinstance(is_coinbase, bool) 176 | # TODO: lock 177 | i = self.details.next() 178 | key_id = self.chain.derive_key_id(3, 0, 0, i, 0) # TODO: manage accounts etc 179 | key_id_hex = key_id.to_hex().decode() 180 | ext_key = self.chain.derive_key(key_id) 181 | child_key = ChildKey(i, key_id.parent_path(), key_id, ext_key) 182 | self.cache[key_id_hex] = child_key 183 | entry = OutputEntry.from_child_key(child_key, value, is_coinbase) 184 | self.outputs[key_id_hex] = entry 185 | # self.save() 186 | return child_key, entry 187 | 188 | def derive_from_entry(self, entry: OutputEntry) -> ChildKey: 189 | key_id = entry.key_id.to_hex().decode() 190 | if key_id not in self.cache: 191 | ext_key = self.chain.derive_key(entry.key_id) 192 | self.cache[key_id] = ChildKey(entry.n_child, entry.root_key_id, entry.key_id, ext_key) 193 | return self.cache[key_id] 194 | 195 | def commit_with_child_key(self, value: int, child_key: ChildKey) -> Commitment: 196 | return self.chain.commit(value, child_key) 197 | 198 | def commit(self, entry: OutputEntry) -> Commitment: 199 | return self.commit_with_child_key(entry.value, self.derive_from_entry(entry)) 200 | 201 | def entry_to_input(self, entry: OutputEntry) -> Input: 202 | commitment = self.commit(entry) 203 | return Input( 204 | OutputFeatures.COINBASE_OUTPUT if entry.is_coinbase else OutputFeatures.DEFAULT_OUTPUT, 205 | commitment 206 | ) 207 | 208 | def entry_to_output(self, entry: OutputEntry) -> Output: 209 | return Output.create( 210 | self.chain, 211 | OutputFeatures.COINBASE_OUTPUT if entry.is_coinbase else OutputFeatures.DEFAULT_OUTPUT, 212 | self.derive_from_entry(entry), 213 | entry.value 214 | ) 215 | 216 | @staticmethod 217 | def open(secp: Secp256k1, location: str): 218 | abs_dir = absolute(location) 219 | assert os.path.isdir(abs_dir) 220 | seed_file = absolute(abs_dir, "wallet.seed") 221 | assert os.path.exists(seed_file) 222 | f = open(seed_file, "r") 223 | wallet = Wallet(secp, location, unhexlify(f.readline().encode())) 224 | wallet.load() 225 | return wallet 226 | 227 | @staticmethod 228 | def create(secp: Secp256k1, location: str, seed: bytes): 229 | assert len(seed) == 32, "Invalid seed length" 230 | abs_dir = absolute(location) 231 | if not os.path.exists(abs_dir): 232 | os.makedirs(abs_dir) 233 | else: 234 | assert os.path.isdir(abs_dir), "Wallet location not a directory" 235 | seed_file = absolute(abs_dir, "wallet.seed") 236 | assert not os.path.exists(seed_file), "Wallet already exists" 237 | f = open(seed_file, "x") 238 | f.write(hexlify(seed).decode()) 239 | f.close() 240 | return Wallet(secp, location, seed) 241 | 242 | @staticmethod 243 | def create_random(secp: Secp256k1, location: str): 244 | return Wallet.create(secp, location, os.urandom(32)) 245 | -------------------------------------------------------------------------------- /grin/keychain.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from hashlib import blake2b 3 | from os import urandom 4 | from typing import List 5 | from secp256k1 import SECRET_KEY_SIZE 6 | from secp256k1.key import SecretKey, PublicKey 7 | from secp256k1.pedersen import Secp256k1, Commitment 8 | from grin.extkey import ExtendedSecretKey, GrinHasher, ChildNumber 9 | 10 | IDENTIFIER_SIZE = 17 11 | 12 | 13 | class Identifier: 14 | def __init__(self): 15 | self.identifier = bytearray([0] * IDENTIFIER_SIZE) 16 | 17 | def __eq__(self, other): 18 | return isinstance(other, Identifier) and self.identifier == other.identifier 19 | 20 | def __str__(self): 21 | return "Identifier<{}>".format(self.to_hex().decode()) 22 | 23 | def __repr__(self): 24 | return self.__str__() 25 | 26 | def to_bytearray(self) -> bytearray: 27 | identifier = self.identifier[:] 28 | return identifier 29 | 30 | def to_hex(self) -> bytes: 31 | return hexlify(self.identifier) 32 | 33 | def to_bip_32_string(self) -> str: 34 | path = KeychainPath.from_identifier(self) 35 | out = "m" 36 | for i in range(path.depth): 37 | out += "/"+str(path.path[i].to_index()) 38 | return out 39 | 40 | def clone(self): 41 | return Identifier.from_bytearray(self.to_bytearray()) 42 | 43 | def serialize_path(self) -> bytearray: 44 | return self.identifier[1:] 45 | 46 | def parent_path(self): 47 | path = KeychainPath.from_identifier(self) 48 | if path.depth > 0: 49 | path.path[path.depth - 1] = ChildNumber.from_index(0) 50 | path.depth = path.depth - 1 51 | return path.to_identifier() 52 | 53 | @staticmethod 54 | def from_bytearray(data: bytearray): 55 | obj = Identifier() 56 | for i in range(min(len(data), IDENTIFIER_SIZE)): 57 | obj.identifier[i] = data[i] 58 | return obj 59 | 60 | @staticmethod 61 | def from_hex(data: bytes): 62 | return Identifier.from_bytearray(bytearray(unhexlify(data))) 63 | 64 | @staticmethod 65 | def from_public_key(secp: Secp256k1, key: PublicKey): 66 | data = key.to_bytearray(secp) 67 | identifier = bytearray(blake2b(bytes(data), digest_size=IDENTIFIER_SIZE).digest()) 68 | return Identifier.from_bytearray(identifier) 69 | 70 | @staticmethod 71 | def from_secret_key(secp: Secp256k1, key: SecretKey): 72 | public = key.to_public_key(secp) 73 | return Identifier.from_public_key(secp, public) 74 | 75 | @staticmethod 76 | def from_serialized_path(length: int, data: bytearray): 77 | identifier = bytearray() 78 | identifier.extend(int.to_bytes(length, 1, "big")) 79 | identifier.extend(data) 80 | return Identifier.from_bytearray(identifier) 81 | 82 | @staticmethod 83 | def random(): 84 | return Identifier.from_bytearray(bytearray(urandom(IDENTIFIER_SIZE))) 85 | 86 | 87 | class ChildKey: 88 | def __init__(self, n_child: int, root_key_id: Identifier, key_id: Identifier, key: ExtendedSecretKey): 89 | self.n_child = n_child 90 | self.root_key_id = root_key_id 91 | self.key_id = key_id 92 | self.key = key 93 | 94 | 95 | # class ExtendedKey: 96 | # def __init__(self, secp: Secp256k1, derived: bytearray): 97 | # assert len(derived) == 64, "Invalid derived size" 98 | # 99 | # key = SecretKey.from_bytearray(secp, derived[0:32]) 100 | # identifier = Identifier.from_secret_key(secp, key) 101 | # 102 | # self.n_child = 0 103 | # self.root_key_id = identifier.clone() 104 | # self.key_id = identifier.clone() 105 | # self.key = key 106 | # self.chain_code = derived[32:64] 107 | # 108 | # def derive(self, secp: Secp256k1, n: int) -> ChildKey: 109 | # n_bytes = n.to_bytes(4, "big") 110 | # seed = self.key.to_bytearray() 111 | # seed.extend(n_bytes) 112 | # derived = bytearray(blake2b(bytes(seed), digest_size=32, key=bytes(self.chain_code)).digest()) 113 | # key = SecretKey.from_bytearray(secp, derived) 114 | # key.add_assign(secp, self.key) 115 | # identifier = Identifier.from_secret_key(secp, key) 116 | # return ChildKey(n, self.root_key_id.clone(), identifier, key) 117 | # 118 | # @staticmethod 119 | # def from_seed(secp: Secp256k1, seed: bytes, password=b""): 120 | # assert len(seed) in (16, 32, 64), "Invalid seed length" 121 | # derived = bytearray(blake2b(blake2b(seed, digest_size=64, key=password).digest(), 122 | # digest_size=64, key=b"Grin/MW Seed").digest()) 123 | # return ExtendedKey(secp, derived) 124 | 125 | 126 | class KeychainPath: 127 | def __init__(self, depth: int, path: List[ChildNumber]): 128 | self.depth = depth 129 | self.path = path 130 | 131 | def to_identifier(self) -> Identifier: 132 | data = bytearray() 133 | data.extend(self.depth.to_bytes(1, "big")) 134 | for i in range(4): 135 | data.extend(self.path[i].to_bytearray()) 136 | return Identifier.from_bytearray(data) 137 | 138 | def last_path_index(self) -> int: 139 | if self.depth == 0: 140 | return 0 141 | return self.path[self.depth-1].to_index() 142 | 143 | @staticmethod 144 | def new(depth: int, d0: int, d1: int, d2: int, d3: int): 145 | return KeychainPath(depth, [ 146 | ChildNumber.from_index(d0), 147 | ChildNumber.from_index(d1), 148 | ChildNumber.from_index(d2), 149 | ChildNumber.from_index(d3) 150 | ]) 151 | 152 | @staticmethod 153 | def from_identifier(identifier: Identifier): 154 | data = identifier.to_bytearray() 155 | return KeychainPath(int.from_bytes(data[0:1], "big"), [ 156 | ChildNumber.from_bytearray(data[1:5]), 157 | ChildNumber.from_bytearray(data[5:9]), 158 | ChildNumber.from_bytearray(data[9:13]), 159 | ChildNumber.from_bytearray(data[13:17]) 160 | ]) 161 | 162 | 163 | class Keychain: 164 | def __init__(self, secp: Secp256k1, seed: bytes): 165 | self.secp = secp 166 | self.hasher = GrinHasher() 167 | self.master = ExtendedSecretKey.new_master(secp, self.hasher, bytearray(seed)) 168 | 169 | def derive_key(self, identifier: Identifier) -> ExtendedSecretKey: 170 | path = KeychainPath.from_identifier(identifier) 171 | key = self.master 172 | for i in range(path.depth): 173 | key = key.ckd_secret(self.secp, self.hasher, path.path[i]) 174 | return key 175 | 176 | def commit(self, amount: int, child_key: ChildKey) -> Commitment: 177 | return self.secp.commit(amount, child_key.key.secret_key) 178 | 179 | def blind_sum(self, blind_sum): 180 | assert isinstance(blind_sum, BlindSum) 181 | pos = [] 182 | for child in blind_sum.positive_child_keys: 183 | pos.append(child.key.secret_key) 184 | for key in blind_sum.positive_blinding_factors: 185 | pos.append(key.to_secret_key(self.secp)) 186 | neg = [] 187 | for child in blind_sum.negative_child_keys: 188 | neg.append(child.key.secret_key) 189 | for key in blind_sum.negative_blinding_factors: 190 | neg.append(key.to_secret_key(self.secp)) 191 | return BlindingFactor.from_secret_key(self.secp.blind_sum(pos, neg)) 192 | 193 | @staticmethod 194 | def from_seed(secp: Secp256k1, seed: bytes): 195 | return Keychain(secp, seed) 196 | 197 | @staticmethod 198 | def root_key_id() -> Identifier: 199 | return KeychainPath.new(0, 0, 0, 0, 0).to_identifier() 200 | 201 | @staticmethod 202 | def derive_key_id(depth: int, d0: int, d1: int, d2: int, d3: int) -> Identifier: 203 | return KeychainPath.new(depth, d0, d1, d2, d3).to_identifier() 204 | 205 | 206 | class BlindingFactor: 207 | def __init__(self): 208 | self.key = bytearray([0] * SECRET_KEY_SIZE) 209 | 210 | def __eq__(self, other): 211 | return isinstance(other, BlindingFactor) and self.to_bytearray() == other.to_bytearray() 212 | 213 | def __str__(self): 214 | return "BlindingFactor<{}>".format(self.to_hex().decode()) 215 | 216 | def __repr__(self): 217 | return self.__str__() 218 | 219 | def to_bytearray(self) -> bytearray: 220 | return self.key[:] 221 | 222 | def to_hex(self) -> bytes: 223 | return hexlify(self.key) 224 | 225 | def to_secret_key(self, secp: Secp256k1) -> SecretKey: 226 | return SecretKey.from_bytearray(secp, self.to_bytearray()) 227 | 228 | @staticmethod 229 | def from_bytearray(data: bytearray): 230 | obj = BlindingFactor() 231 | for i in range(min(len(data), SECRET_KEY_SIZE)): 232 | obj.key[i] = data[i] 233 | return obj 234 | 235 | @staticmethod 236 | def from_hex(data: bytes): 237 | return BlindingFactor.from_bytearray(bytearray(unhexlify(data))) 238 | 239 | @staticmethod 240 | def from_secret_key(key: SecretKey): 241 | return BlindingFactor.from_bytearray(key.to_bytearray()) 242 | 243 | 244 | class BlindSum: 245 | def __init__(self): 246 | self.positive_child_keys = [] 247 | self.negative_child_keys = [] 248 | self.positive_blinding_factors = [] 249 | self.negative_blinding_factors = [] 250 | 251 | def add_child_key(self, key: ChildKey): 252 | self.positive_child_keys.append(key) 253 | return self 254 | 255 | def sub_child_key(self, key: ChildKey): 256 | self.negative_child_keys.append(key) 257 | return self 258 | 259 | def add_blinding_factor(self, blind: BlindingFactor): 260 | self.positive_blinding_factors.append(blind) 261 | 262 | def sub_blinding_factor(self, blind: BlindingFactor): 263 | self.negative_blinding_factors.append(blind) 264 | -------------------------------------------------------------------------------- /tests/test_extkey.py: -------------------------------------------------------------------------------- 1 | from binascii import unhexlify 2 | from pytest import raises 3 | from typing import List 4 | from secp256k1 import Secp256k1, FLAG_ALL 5 | from grin.extkey import ChildNumber, ReferenceHasher, ExtendedSecretKey, ExtendedPublicKey, HardenedIndexError 6 | from grin.util import base58_encode, base58check_encode, base58_decode, base58check_decode 7 | 8 | 9 | def test_base58_encode(): 10 | assert base58_encode(bytearray([0])) == b"1" 11 | assert base58_encode(bytearray([1])) == b"2" 12 | assert base58_encode(bytearray([58])) == b"21" 13 | assert base58_encode(bytearray([13, 36])) == b"211" 14 | assert base58_encode(bytearray([0, 13, 36])) == b"1211" 15 | assert base58_encode(bytearray([0, 0, 0, 0, 13, 36])) == b"1111211" 16 | assert base58check_encode(bytearray(unhexlify(b"00f8917303bfa8ef24f292e8fa1419b20460ba064d"))) \ 17 | == b"1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH" 18 | 19 | 20 | def test_base58_decode(): 21 | assert base58_decode(b"1") == bytearray([0]) 22 | assert base58_decode(b"2") == bytearray([1]) 23 | assert base58_decode(b"21") == bytearray([58]) 24 | assert base58_decode(b"211") == bytearray([13, 36]) 25 | assert base58_decode(b"1211") == bytearray([0, 13, 36]) 26 | assert base58_decode(b"111211") == bytearray([0, 0, 0, 13, 36]) 27 | assert base58check_decode(b"1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH") \ 28 | == bytearray(unhexlify(b"00f8917303bfa8ef24f292e8fa1419b20460ba064d")) 29 | 30 | 31 | def test_base58_roundtrip(): 32 | s = b"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" 33 | v = base58check_decode(s) 34 | assert base58check_encode(v) == s 35 | assert base58check_decode(base58check_encode(v)) == v 36 | 37 | 38 | def check_path(secp: Secp256k1, seed: bytearray, path: List[ChildNumber], 39 | expected_secret: bytes, expected_public: bytes): 40 | hasher = ReferenceHasher() 41 | secret = ExtendedSecretKey.new_master(secp, hasher, seed) 42 | public = ExtendedPublicKey.from_secret(secp, hasher, secret) 43 | assert secret.derive_secret(secp, hasher, path).to_base58check() == expected_secret 44 | 45 | hardened = False 46 | for i in path: 47 | if i.is_hardened(): 48 | hardened = True 49 | break 50 | 51 | # This should fail if any of the indices are hardened 52 | if hardened: 53 | with raises(HardenedIndexError): 54 | public.derive_public(secp, hasher, path) 55 | else: 56 | assert public.derive_public(secp, hasher, path).to_base58check(secp) == expected_public 57 | 58 | # Check keys at each step of the path 59 | for i in path: 60 | secret = secret.ckd_secret(secp, hasher, i) 61 | if i.is_normal(): 62 | public2 = public.ckd_public(secp, hasher, i) 63 | public = ExtendedPublicKey.from_secret(secp, hasher, secret) 64 | assert public == public2 65 | else: 66 | with raises(HardenedIndexError): 67 | public.ckd_public(secp, hasher, i) 68 | public = ExtendedPublicKey.from_secret(secp, hasher, secret) 69 | 70 | # Test serialization 71 | assert secret.to_base58check() == expected_secret 72 | assert public.to_base58check(secp) == expected_public 73 | 74 | # Test deserialization 75 | assert secret == ExtendedSecretKey.from_base58check(secp, expected_secret) 76 | # assert public == ExtendedPublicKey.from_base58check(secp, expected_public) 77 | 78 | 79 | def test_path_1(): 80 | secp = Secp256k1(None, FLAG_ALL) 81 | seed = bytearray(unhexlify(b"000102030405060708090a0b0c0d0e0f")) 82 | 83 | # m 84 | check_path(secp, seed, [], 85 | b"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", 86 | b"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8") 87 | 88 | # m/0h 89 | check_path(secp, seed, [ChildNumber.from_hardened_index(0)], 90 | b"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", 91 | b"xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw") 92 | 93 | # m/0h/1 94 | check_path(secp, seed, [ChildNumber.from_hardened_index(0), ChildNumber.from_normal_index(1)], 95 | b"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", 96 | b"xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ") 97 | 98 | # m/0h/1/2h 99 | check_path(secp, seed, [ChildNumber.from_hardened_index(0), ChildNumber.from_normal_index(1), 100 | ChildNumber.from_hardened_index(2)], 101 | b"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", 102 | b"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5") 103 | 104 | # m/0h/1/2h/2 105 | check_path(secp, seed, [ChildNumber.from_hardened_index(0), ChildNumber.from_normal_index(1), 106 | ChildNumber.from_hardened_index(2), ChildNumber.from_normal_index(2)], 107 | b"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", 108 | b"xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV") 109 | 110 | # m/0h/1/2h/2/1000000000 111 | check_path(secp, seed, [ChildNumber.from_hardened_index(0), ChildNumber.from_normal_index(1), 112 | ChildNumber.from_hardened_index(2), ChildNumber.from_normal_index(2), 113 | ChildNumber.from_normal_index(1000000000)], 114 | b"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", 115 | b"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy") 116 | 117 | 118 | def test_path_2(): 119 | secp = Secp256k1(None, FLAG_ALL) 120 | seed = bytearray(unhexlify(b"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542")) 121 | 122 | # m 123 | check_path(secp, seed, [], 124 | b"xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", 125 | b"xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB") 126 | 127 | # m/0 128 | check_path(secp, seed, [ChildNumber.from_normal_index(0)], 129 | b"xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", 130 | b"xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH") 131 | 132 | # m/0/2147483647h 133 | check_path(secp, seed, [ChildNumber.from_normal_index(0), ChildNumber.from_hardened_index(2147483647)], 134 | b"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", 135 | b"xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a") 136 | 137 | # m/0/2147483647h/1 138 | check_path(secp, seed, [ChildNumber.from_normal_index(0), ChildNumber.from_hardened_index(2147483647), 139 | ChildNumber.from_normal_index(1)], 140 | b"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", 141 | b"xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon") 142 | 143 | # m/0/2147483647h/1/2147483646h 144 | check_path(secp, seed, [ChildNumber.from_normal_index(0), ChildNumber.from_hardened_index(2147483647), 145 | ChildNumber.from_normal_index(1), ChildNumber.from_hardened_index(2147483646)], 146 | b"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", 147 | b"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL") 148 | 149 | # m/0/2147483647h/1/2147483646h/2 150 | check_path(secp, seed, [ChildNumber.from_normal_index(0), ChildNumber.from_hardened_index(2147483647), 151 | ChildNumber.from_normal_index(1), ChildNumber.from_hardened_index(2147483646), 152 | ChildNumber.from_normal_index(2)], 153 | b"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", 154 | b"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt") 155 | 156 | 157 | def test_path_3(): 158 | secp = Secp256k1(None, FLAG_ALL) 159 | seed = bytearray(unhexlify(b"4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be")) 160 | 161 | # m 162 | check_path(secp, seed, [], 163 | b"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", 164 | b"xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13") 165 | 166 | # m/0h 167 | check_path(secp, seed, [ChildNumber.from_hardened_index(0)], 168 | b"xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", 169 | b"xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y") 170 | -------------------------------------------------------------------------------- /grin/btc.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from typing import Optional, List 3 | from secp256k1.key import SecretKey, PublicKey 4 | from secp256k1.pedersen import Secp256k1 5 | from grin.util import hash160, hash256, base58check_encode, base58check_decode, var_int_encode, script_write_bytes 6 | 7 | PREFIX_TN_PUBKEY_HASH = 0x6F 8 | PREFIX_TN_SCRIPT_HASH = 0xC4 9 | PREFIX_PUBKEY_HASH = 0x00 10 | PREFIX_SCRIPT_HASH = 0x05 11 | 12 | OP_FALSE = 0x00 13 | OP_0 = OP_FALSE 14 | OP_PUSH_4 = 0x04 15 | OP_PUSH_20 = 0x14 16 | OP_PUSH_33 = 0x21 17 | OP_TRUE = 0x51 18 | OP_1 = OP_TRUE 19 | OP_2 = 0x52 20 | OP_IF = 0x63 21 | OP_ELSE = 0x67 22 | OP_ENDIF = 0x68 23 | OP_DROP = 0x75 24 | OP_DUP = 0x76 25 | OP_EQUAL = 0x87 26 | OP_EQUALVERIFY = 0x88 27 | OP_HASH160 = 0xa9 28 | OP_CHECKSIG = 0xac 29 | OP_CHECKMULTISIG = 0xae 30 | OP_CHECKLOCKTIMEVERIFY = 0xb1 31 | 32 | 33 | class TXID: 34 | def __init__(self, hashed: bytearray): 35 | assert len(hashed) == 32 36 | self.hashed = hashed 37 | 38 | def __str__(self): 39 | return "TXID<{}>".format(self.to_hex().decode()) 40 | 41 | def __repr__(self): 42 | return self.__str__() 43 | 44 | def to_bytearray(self, reverse=False) -> bytearray: 45 | data = self.hashed[:] 46 | if reverse: 47 | data.reverse() 48 | return data 49 | 50 | def to_hex(self, reverse=True) -> bytes: 51 | return hexlify(self.to_bytearray(reverse)) 52 | 53 | @staticmethod 54 | def from_bytearray(data: bytearray, reverse=False): 55 | hashed = data[:] 56 | if reverse: 57 | hashed.reverse() 58 | return TXID(hashed) 59 | 60 | @staticmethod 61 | def from_hex(data: bytes, reverse=True): 62 | return TXID.from_bytearray(bytearray(unhexlify(data)), reverse) 63 | 64 | 65 | class OutputPoint: 66 | def __init__(self, txid: TXID, index: int): 67 | self.txid = txid 68 | self.index = index 69 | 70 | def to_bytearray(self) -> bytearray: 71 | data = bytearray() 72 | data.extend(self.txid.to_bytearray()) 73 | data.extend(self.index.to_bytes(4, "big")) 74 | return data 75 | 76 | def to_hex(self) -> bytes: 77 | return hexlify(self.to_bytearray()) 78 | 79 | @staticmethod 80 | def from_bytearray(data: bytearray): 81 | return OutputPoint(TXID.from_bytearray(data[:32]), int.from_bytes(data[32:36], "big")) 82 | 83 | @staticmethod 84 | def from_hex(data: bytes): 85 | return OutputPoint.from_bytearray(bytearray(unhexlify(data))) 86 | 87 | 88 | class Address: 89 | def __init__(self, hashed: bytearray, pubkey=True, mainnet=True): 90 | assert len(hashed) == 20 91 | self.pubkey = pubkey 92 | self.mainnet = mainnet 93 | self.hashed = hashed 94 | 95 | def __str__(self): 96 | return "Address<{}>".format(self.to_base58check().decode()) 97 | 98 | def __repr__(self): 99 | return self.__str__() 100 | 101 | def to_bytearray(self, prefix=True) -> bytearray: 102 | data = self.hashed[:] 103 | if prefix: 104 | data.insert(0, self.prefix()) 105 | return data 106 | 107 | def to_base58check(self) -> bytes: 108 | return base58check_encode(self.to_bytearray()) 109 | 110 | def prefix(self) -> int: 111 | if self.mainnet: 112 | return PREFIX_PUBKEY_HASH if self.pubkey else PREFIX_SCRIPT_HASH 113 | return PREFIX_TN_PUBKEY_HASH if self.pubkey else PREFIX_TN_SCRIPT_HASH 114 | 115 | def is_mainnet(self) -> bool: 116 | return self.mainnet 117 | 118 | def is_testnet(self) -> bool: 119 | return not self.mainnet 120 | 121 | def is_pubkey_hash(self) -> bool: 122 | return self.pubkey 123 | 124 | def is_script_hash(self) -> bool: 125 | return not self.pubkey 126 | 127 | @staticmethod 128 | def from_bytearray(data: bytearray): 129 | prefixes = { 130 | PREFIX_TN_PUBKEY_HASH: [True, False], 131 | PREFIX_TN_SCRIPT_HASH: [False, False], 132 | PREFIX_PUBKEY_HASH: [True, True], 133 | PREFIX_SCRIPT_HASH: [False, True] 134 | } 135 | assert data[0] in prefixes 136 | flags = prefixes[data[0]] 137 | return Address(data[1:], flags[0], flags[1]) 138 | 139 | @staticmethod 140 | def from_base58check(data: bytes): 141 | return Address.from_bytearray(base58check_decode(data)) 142 | 143 | @staticmethod 144 | def from_public_key(secp: Secp256k1, key: PublicKey, mainnet=True): 145 | return Address(hash160(key.to_bytearray(secp)), True, mainnet) 146 | 147 | @staticmethod 148 | def from_script(script: bytearray, mainnet=True): 149 | return Address(hash160(script), False, mainnet) 150 | 151 | 152 | class Script: 153 | @staticmethod 154 | def is_p2pkh(script: bytearray) -> bool: 155 | return len(script) == 25 and script[0] == 0x76 and script[1] == 0xa9 and script[2] == 0x14 and \ 156 | script[23] == 0x88 and script[24] == 0xac 157 | 158 | @staticmethod 159 | def p2(address: Address): 160 | if address.is_script_hash(): 161 | return Script.p2sh(address) 162 | return Script.p2pkh(address) 163 | 164 | @staticmethod 165 | def p2pkh(pubkey_hash: Address) -> bytearray: 166 | data = bytearray() 167 | data.append(OP_DUP) 168 | data.append(OP_HASH160) 169 | data.append(OP_PUSH_20) 170 | data.extend(pubkey_hash.to_bytearray(False)) 171 | data.append(OP_EQUALVERIFY) 172 | data.append(OP_CHECKSIG) 173 | return data 174 | 175 | @staticmethod 176 | def p2sh(script_hash: Address) -> bytearray: 177 | data = bytearray() 178 | data.append(OP_HASH160) 179 | data.append(OP_PUSH_20) 180 | data.extend(script_hash.to_bytearray(False)) 181 | data.append(OP_EQUAL) 182 | return data 183 | 184 | @staticmethod 185 | def multisig_refund(secp: Secp256k1, key_a: PublicKey, key_b: PublicKey, 186 | refund_key: PublicKey, timelock: int) -> bytearray: 187 | data = bytearray() 188 | data.append(OP_IF) 189 | data.append(OP_PUSH_4) 190 | data.extend(timelock.to_bytes(4, "little")) 191 | data.append(OP_CHECKLOCKTIMEVERIFY) 192 | data.append(OP_DROP) 193 | data.append(OP_PUSH_33) 194 | data.extend(refund_key.to_bytearray(secp)) 195 | data.append(OP_CHECKSIG) 196 | data.append(OP_ELSE) 197 | data.append(OP_2) 198 | data.append(OP_PUSH_33) 199 | data.extend(key_a.to_bytearray(secp)) 200 | data.append(OP_PUSH_33) 201 | data.extend(key_b.to_bytearray(secp)) 202 | data.append(OP_2) 203 | data.append(OP_CHECKMULTISIG) 204 | data.append(OP_ENDIF) 205 | return data 206 | 207 | 208 | class Input: 209 | def __init__(self, prev_tx_hash: TXID, prev_tx_index: int, 210 | prev_script_pubkey: bytearray, script_sig: bytearray, sequence: Optional[int]): 211 | self.prev_tx_hash = prev_tx_hash 212 | self.prev_tx_index = prev_tx_index 213 | self.prev_script_pubkey = prev_script_pubkey 214 | self.script_sig = script_sig 215 | self.sequence = 0xFFFFFFFF if sequence is None else sequence 216 | 217 | def to_bytearray(self, for_signature=None) -> bytearray: 218 | data = bytearray() 219 | data.extend(self.prev_tx_hash.to_bytearray()) 220 | data.extend(self.prev_tx_index.to_bytes(4, "little")) 221 | if for_signature is None: 222 | data.extend(var_int_encode(len(self.script_sig))) 223 | data.extend(self.script_sig) 224 | else: 225 | if for_signature: 226 | data.extend(var_int_encode(len(self.prev_script_pubkey))) 227 | data.extend(self.prev_script_pubkey) 228 | else: 229 | data.append(0x00) 230 | data.extend(self.sequence.to_bytes(4, "little")) 231 | return data 232 | 233 | 234 | class Output: 235 | def __init__(self, value: int, script_pubkey: bytearray): 236 | self.value = value 237 | self.script = script_pubkey 238 | 239 | def to_bytearray(self) -> bytearray: 240 | data = bytearray() 241 | data.extend(self.value.to_bytes(8, "little")) 242 | data.extend(var_int_encode(len(self.script))) 243 | data.extend(self.script) 244 | return data 245 | 246 | 247 | class Transaction: 248 | def __init__(self, version: int, inputs: List[Input], outputs: List[Output], lock_time: int): 249 | self.version = version 250 | self.inputs = inputs 251 | self.outputs = outputs 252 | self.lock_time = lock_time 253 | 254 | def to_bytearray(self, sign_input=None) -> bytearray: 255 | data = bytearray() 256 | data.extend(self.version.to_bytes(4, "little")) 257 | data.extend(var_int_encode(len(self.inputs))) 258 | for i in range(len(self.inputs)): 259 | input = self.inputs[i] 260 | data.extend(input.to_bytearray(None if sign_input is None else (True if sign_input == i else False))) 261 | data.extend(var_int_encode(len(self.outputs))) 262 | for output in self.outputs: 263 | data.extend(output.to_bytearray()) 264 | data.extend(self.lock_time.to_bytes(4, "little")) 265 | return data 266 | 267 | def to_hex(self) -> bytes: 268 | return hexlify(self.to_bytearray()) 269 | 270 | def txid(self) -> TXID: 271 | return TXID.from_bytearray(hash256(self.to_bytearray())) 272 | 273 | def add_input(self, input: Input): 274 | self.inputs.append(input) 275 | 276 | def add_output(self, output: Output): 277 | self.outputs.append(output) 278 | 279 | def raw_signature(self, secp: Secp256k1, i: int, secret_key: SecretKey) -> bytearray: 280 | data = self.to_bytearray(i) 281 | data.extend(bytearray([1, 0, 0, 0])) 282 | signature = secp.sign(secret_key, hash256(data)) 283 | signature.append(0x01) 284 | return signature 285 | 286 | def sign(self, secp: Secp256k1, i: int, secret_key: SecretKey): 287 | script_sig = bytearray() 288 | signature = self.raw_signature(secp, i, secret_key) 289 | script_sig.extend(script_write_bytes(len(signature))) 290 | script_sig.extend(signature) 291 | public_key = secret_key.to_public_key(secp).to_bytearray(secp) 292 | script_sig.extend(script_write_bytes(len(public_key))) 293 | script_sig.extend(public_key) 294 | self.inputs[i].script_sig = script_sig 295 | -------------------------------------------------------------------------------- /secp256k1/key.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from eth_hash.auto import keccak 3 | from os import urandom 4 | from secp256k1 import Secp256k1, SECRET_KEY_SIZE, PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE_COMPRESSED, \ 5 | EC_COMPRESSED, EC_UNCOMPRESSED 6 | from ._libsecp256k1 import ffi, lib 7 | 8 | 9 | class SecretKey: 10 | def __init__(self): 11 | # Byte array containing the key, use bytes() before passing to secp256k1 12 | self.key = bytearray([0] * SECRET_KEY_SIZE) 13 | 14 | def __eq__(self, other): 15 | return isinstance(other, SecretKey) and self.key == other.key 16 | 17 | def __str__(self): 18 | return "SecretKey<{}>".format(self.to_hex().decode()) 19 | 20 | def __repr__(self): 21 | return self.__str__() 22 | 23 | def to_bytearray(self) -> bytearray: 24 | return self.key[:] 25 | 26 | def to_hex(self) -> bytes: 27 | return hexlify(self.key) 28 | 29 | def to_public_key(self, secp: Secp256k1): 30 | return PublicKey.from_secret_key(secp, self) 31 | 32 | def clone(self): 33 | obj = SecretKey() 34 | obj.key = self.key[:] 35 | return obj 36 | 37 | # b: a -> a+b 38 | def add_assign(self, secp: Secp256k1, other): 39 | assert isinstance(other, SecretKey) 40 | key = ffi.new("char [32]", bytes(self.key)) 41 | res = lib.secp256k1_ec_privkey_tweak_add(secp.ctx, key, bytes(other.key)) 42 | assert res, "Unable to add in place" 43 | self.key = bytearray(ffi.buffer(key, 32)) 44 | 45 | # b: a+b 46 | def add(self, secp: Secp256k1, other): 47 | obj = self.clone() 48 | obj.add_assign(secp, other) 49 | return obj 50 | 51 | # b: a -> a*b 52 | def mul_assign(self, secp: Secp256k1, other): 53 | assert isinstance(other, SecretKey) 54 | key = ffi.new("char [32]", bytes(self.key)) 55 | res = lib.secp256k1_ec_privkey_tweak_mul(secp.ctx, key, bytes(other.key)) 56 | assert res, "Unable to multiply in place" 57 | self.key = bytearray(ffi.buffer(key, 32)) 58 | 59 | # b: a*b 60 | def mul(self, secp: Secp256k1, other): 61 | obj = self.clone() 62 | obj.mul_assign(secp, other) 63 | return obj 64 | 65 | # a -> -a 66 | def negate_assign(self, secp: Secp256k1): 67 | key = ffi.new("char [32]", bytes(self.key)) 68 | res = lib.secp256k1_ec_privkey_negate(secp.ctx, key) 69 | assert res, "Unable to negate in place" 70 | self.key = bytearray(ffi.buffer(key, 32)) 71 | 72 | # -a 73 | def negate(self, secp: Secp256k1): 74 | obj = self.clone() 75 | obj.negate_assign(secp) 76 | return obj 77 | 78 | @staticmethod 79 | def from_bytearray(secp: Secp256k1, data: bytearray): 80 | assert len(data) == SECRET_KEY_SIZE, "Invalid private key size" 81 | res = lib.secp256k1_ec_seckey_verify(secp.ctx, bytes(data)) 82 | assert res, "Invalid private key" 83 | obj = SecretKey() 84 | obj.key = data[:] 85 | return obj 86 | 87 | @staticmethod 88 | def from_hex(secp: Secp256k1, data: bytes): 89 | return SecretKey.from_bytearray(secp, bytearray(unhexlify(data))) 90 | 91 | @staticmethod 92 | def random(secp: Secp256k1): 93 | try: 94 | return SecretKey.from_bytearray(secp, bytearray(urandom(32))) 95 | except AssertionError: 96 | # There is a very small chance of producing a number larger than the curve order 97 | return SecretKey.random(secp) 98 | 99 | 100 | class PublicKey: 101 | def __init__(self, secp: Secp256k1): 102 | self.key = ffi.new("secp256k1_pubkey *") 103 | self.secp = secp 104 | 105 | def __eq__(self, other): 106 | return self.to_bytearray(self.secp) == other.to_bytearray(other.secp) 107 | 108 | def __str__(self): 109 | return "PublicKey<{}>".format(self.to_hex(self.secp, compressed=True).decode()) 110 | 111 | def __repr__(self): 112 | return self.__str__() 113 | 114 | def to_bytearray(self, secp: Secp256k1, compressed=True) -> bytearray: 115 | size = PUBLIC_KEY_SIZE_COMPRESSED if compressed else PUBLIC_KEY_SIZE 116 | flag = EC_COMPRESSED if compressed else EC_UNCOMPRESSED 117 | out = ffi.new("char [%d]" % size) 118 | out_size = ffi.new("size_t *", size) 119 | res = lib.secp256k1_ec_pubkey_serialize(secp.ctx, out, out_size, self.key, flag) 120 | assert res, "Unable to serialize" 121 | return bytearray(ffi.buffer(out, size)) 122 | 123 | def to_hex(self, secp: Secp256k1, compressed=True) -> bytes: 124 | return hexlify(self.to_bytearray(secp, compressed)) 125 | 126 | def clone(self, secp: Secp256k1): 127 | return PublicKey.from_bytearray(secp, self.to_bytearray(secp)) 128 | 129 | # b: A -> A+b*G - Add a scalar in place 130 | def add_scalar_assign(self, secp: Secp256k1, other: SecretKey): 131 | res = lib.secp256k1_ec_pubkey_tweak_add(secp.ctx, self.key, bytes(other.key)) 132 | assert res, "Unable to add scalar in place" 133 | 134 | # b: A+b*G - Add a scalar 135 | def add_scalar(self, secp: Secp256k1, other: SecretKey): 136 | obj = self.clone(secp) 137 | obj.add_scalar_assign(secp, other) 138 | return obj 139 | 140 | # B: A -> A+B - Add a public key in place 141 | def add_assign(self, secp: Secp256k1, other): 142 | obj = self.add(secp, other) 143 | self.key = obj.key 144 | 145 | # B: A+B - Add a public key 146 | def add(self, secp: Secp256k1, other): 147 | assert isinstance(other, PublicKey) 148 | obj = PublicKey.from_combination(secp, [self, other]) 149 | return obj 150 | 151 | # b: A -> b*A - Multiple the public key by a scalar in place 152 | def mul_assign(self, secp: Secp256k1, other: SecretKey): 153 | res = lib.secp256k1_ec_pubkey_tweak_mul(secp.ctx, self.key, bytes(other.key)) 154 | assert res, "Unable to multiply in place" 155 | 156 | # b: b*A - Multiple the public key by a scalar 157 | def mul(self, secp: Secp256k1, other: SecretKey): 158 | obj = self.clone(secp) 159 | obj.mul_assign(secp, other) 160 | return obj 161 | 162 | # A -> -A 163 | def negate_assign(self, secp: Secp256k1): 164 | res = lib.secp256k1_ec_pubkey_negate(secp.ctx, self.key) 165 | assert res, "Unable to negate in place" 166 | 167 | # -A 168 | def negate(self, secp: Secp256k1): 169 | obj = self.clone(secp) 170 | obj.negate_assign(secp) 171 | return obj 172 | 173 | @staticmethod 174 | def from_bytearray(secp: Secp256k1, data: bytearray): 175 | size = len(data) 176 | assert size in (PUBLIC_KEY_SIZE_COMPRESSED, PUBLIC_KEY_SIZE), "Invalid public key size" 177 | obj = PublicKey(secp) 178 | res = lib.secp256k1_ec_pubkey_parse(secp.ctx, obj.key, bytes(data), size) 179 | assert res, "Invalid public key" 180 | return obj 181 | 182 | @staticmethod 183 | def from_hex(secp: Secp256k1, data: bytes): 184 | return PublicKey.from_bytearray(secp, bytearray(unhexlify(data))) 185 | 186 | @staticmethod 187 | def from_secret_key(secp: Secp256k1, secret: SecretKey): 188 | obj = PublicKey(secp) 189 | res = lib.secp256k1_ec_pubkey_create(secp.ctx, obj.key, bytes(secret.key)) 190 | assert res, "Invalid secret key" 191 | return obj 192 | 193 | @staticmethod 194 | def from_combination(secp: Secp256k1, pos_keys, neg_keys=None): 195 | if neg_keys is None: 196 | assert len(pos_keys) > 0 197 | else: 198 | assert len(pos_keys) > 0 or len(neg_keys) > 0 199 | obj = PublicKey(secp) 200 | items = [] 201 | for key in pos_keys: 202 | if isinstance(key, SecretKey): 203 | items.append(key.to_public_key(secp).key) 204 | else: 205 | assert isinstance(key, PublicKey), "Input not all instance of SecretKey or PublicKey" 206 | items.append(key.key) 207 | if isinstance(neg_keys, list) and len(neg_keys) > 0: 208 | neg_sum = PublicKey.from_combination(secp, neg_keys) 209 | neg_sum.negate_assign(secp) 210 | items.append(neg_sum.key) 211 | res = lib.secp256k1_ec_pubkey_combine(secp.ctx, obj.key, items, len(items)) 212 | assert res, "Unable to combine keys" 213 | return obj 214 | 215 | 216 | class Signature: 217 | def __init__(self, signature: bytearray): 218 | self.signature = signature 219 | 220 | def __eq__(self, other): 221 | return isinstance(other, Signature) and self.signature == other.signature 222 | 223 | def __str__(self): 224 | return "Signature<{}>".format(self.to_hex().decode()) 225 | 226 | def __repr__(self): 227 | return self.__str__() 228 | 229 | def scalar(self, secp: Secp256k1) -> SecretKey: 230 | return SecretKey.from_bytearray(secp, self.signature[32:64]) 231 | 232 | def to_bytearray(self, secp: Secp256k1, compact=False) -> bytearray: 233 | if not compact: 234 | return self.signature[:] 235 | signature = ffi.new("secp256k1_ecdsa_signature *", [bytes(self.signature)]) 236 | output = ffi.new("char [64]") 237 | res = lib.secp256k1_ecdsa_signature_serialize_compact(secp.ctx, output, signature) 238 | assert res, "Unable to serialize signature" 239 | return bytearray(ffi.buffer(output, 64)) 240 | 241 | def to_hex(self) -> bytes: 242 | return hexlify(self.signature) 243 | 244 | def normalize_s(self, secp: Secp256k1): 245 | signature_in = ffi.new("secp256k1_ecdsa_signature *", [bytes(self.signature)]) 246 | signature_out = ffi.new("secp256k1_ecdsa_signature *") 247 | lib.secp256k1_ecdsa_signature_normalize(secp.ctx, signature_out, signature_in) 248 | self.signature = bytearray(ffi.buffer(signature_out, 64)) 249 | 250 | @staticmethod 251 | def from_bytearray(secp: Secp256k1, signature: bytearray, compact=False): 252 | if not compact: 253 | return Signature(signature[:]) 254 | signature_out = ffi.new("secp256k1_ecdsa_signature *") 255 | res = lib.secp256k1_ecdsa_signature_parse_compact(secp.ctx, signature_out, bytes(signature)) 256 | assert res, "Unable to parse signature" 257 | return Signature(ffi.buffer(signature_out, 64)) 258 | 259 | @staticmethod 260 | def from_hex(data: bytes): 261 | return Signature(unhexlify(data)) 262 | 263 | 264 | def ethereum_address(secp: Secp256k1, public_key: PublicKey) -> bytes: 265 | return b"0x"+hexlify(keccak(bytes(public_key.to_bytearray(secp, False)[1:]))[12:]) 266 | -------------------------------------------------------------------------------- /grin/transaction.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from enum import Enum 3 | from binascii import hexlify 4 | from grin.util import hasher, sort_by_hash, MILLI_GRIN_UNIT 5 | from grin.keychain import Keychain, BlindingFactor, ChildKey 6 | from grin.proof import create as create_proof, verify as verify_proof 7 | import grin.aggsig as aggsig 8 | from secp256k1.key import SecretKey, Signature, PUBLIC_KEY_SIZE_COMPRESSED 9 | from secp256k1.pedersen import Secp256k1, Commitment, RangeProof 10 | 11 | 12 | def tx_fee(n_input: int, n_output: int, base_fee: Optional[int]): 13 | if base_fee is None: 14 | base_fee = MILLI_GRIN_UNIT 15 | weight = max(1, 1+4*n_output-n_input) 16 | return weight*base_fee 17 | 18 | 19 | class OutputFeatures(Enum): 20 | DEFAULT_OUTPUT = 0 21 | COINBASE_OUTPUT = 1 22 | 23 | 24 | class Input: 25 | def __init__(self, features: OutputFeatures, commit: Commitment): 26 | self.features = features 27 | self.commit = commit 28 | 29 | def __repr__(self): 30 | return "Input".format(self.features.value, self.commit) 31 | 32 | def to_bytearray(self, secp: Secp256k1) -> bytearray: 33 | data = bytearray() 34 | data.extend(bytearray(int(self.features.value).to_bytes(1, "big"))) 35 | data.extend(self.commit.to_bytearray(secp)) 36 | return data 37 | 38 | def to_dict(self, secp: Secp256k1, short=False) -> dict: 39 | return { 40 | "features": self.features.value if short else {"bits": self.features.value}, 41 | "commit": self.commit.to_hex(secp).decode() if short else list(self.commit.to_bytearray(secp)) 42 | } 43 | 44 | def hash(self, secp: Secp256k1) -> bytes: 45 | return hasher(self.to_bytearray(secp)) 46 | 47 | @staticmethod 48 | def from_dict(secp: Secp256k1, dct: dict, short=False): 49 | features = OutputFeatures(dct['features']) if short else OutputFeatures(dct['features']['bits']) 50 | commit = Commitment.from_hex(secp, dct['commit'].encode()) if short else \ 51 | Commitment.from_bytearray(secp, bytearray(dct['commit'])) 52 | return Input(features, commit) 53 | 54 | 55 | class Output: 56 | def __init__(self, features: OutputFeatures, commit: Commitment, range_proof: RangeProof): 57 | self.features = features 58 | self.commit = commit 59 | self.range_proof = range_proof 60 | 61 | def __repr__(self): 62 | return "Output".format(self.features.value, self.commit, self.range_proof) 63 | 64 | def to_bytearray(self, secp: Secp256k1, for_hash=False) -> bytearray: 65 | data = bytearray() 66 | data.extend(bytearray(int(self.features.value).to_bytes(1, "big"))) 67 | data.extend(self.commit.to_bytearray(secp)) 68 | if not for_hash: 69 | proof = self.range_proof.to_bytearray() 70 | data.extend(bytearray(len(proof).to_bytes(8, "big"))) 71 | data.extend(proof) 72 | return data 73 | 74 | def to_dict(self, secp, short=False) -> dict: 75 | return { 76 | "features": self.features.value if short else {"bits": self.features.value}, 77 | "commit": self.commit.to_hex(secp).decode() if short else list(self.commit.to_bytearray(secp)), 78 | "proof": self.range_proof.to_hex().decode() if short else list(self.range_proof.to_bytearray()) 79 | } 80 | 81 | def hash(self, secp: Secp256k1) -> bytes: 82 | return hasher(self.to_bytearray(secp, True)) 83 | 84 | def verify(self, secp: Secp256k1) -> bool: 85 | return verify_proof(secp, self.commit, self.range_proof, bytearray()) 86 | 87 | @staticmethod 88 | def from_dict(secp: Secp256k1, dct: dict, short=False): 89 | features = OutputFeatures(dct['features']) if short else OutputFeatures(dct['features']['bits']) 90 | commit = Commitment.from_hex(secp, dct['commit'].encode()) if short else \ 91 | Commitment.from_bytearray(secp, bytearray(dct['commit'])) 92 | proof = RangeProof.from_hex(dct['proof'].encode()) if short else \ 93 | RangeProof.from_bytearray(bytearray(dct['proof'])) 94 | return Output(features, commit, proof) 95 | 96 | @staticmethod 97 | def create(chain: Keychain, features: OutputFeatures, child_key: ChildKey, amount: int): 98 | commit = chain.commit(amount, child_key) 99 | proof = create_proof(chain.secp, child_key.key, amount, commit, bytearray()) 100 | return Output(features, commit, proof) 101 | 102 | 103 | class Kernel: 104 | def __init__(self, features: int, fee: int, lock_height: int, excess: Optional[Commitment], 105 | excess_signature: Optional[Signature]): 106 | self.features = features 107 | self.fee = fee 108 | self.lock_height = lock_height 109 | self.excess = excess 110 | self.excess_signature = excess_signature 111 | 112 | def to_bytearray(self, secp: Secp256k1) -> bytearray: 113 | data = bytearray() 114 | data.extend(bytearray(self.features.to_bytes(1, "big"))) 115 | data.extend(bytearray(self.fee.to_bytes(8, "big"))) 116 | data.extend(bytearray(self.lock_height.to_bytes(8, "big"))) 117 | data.extend( 118 | bytearray([0] * PUBLIC_KEY_SIZE_COMPRESSED) if self.excess is None else self.excess.to_bytearray(secp) 119 | ) 120 | data.extend( 121 | bytearray([0] * 64) if self.excess_signature is None else self.excess_signature.to_bytearray(secp, False) 122 | ) 123 | return data 124 | 125 | def hash(self, secp: Secp256k1) -> bytes: 126 | return hasher(self.to_bytearray(secp)) 127 | 128 | def to_dict(self, secp: Secp256k1, short=False) -> dict: 129 | excess = bytearray([0] * PUBLIC_KEY_SIZE_COMPRESSED) if self.excess is None else self.excess.to_bytearray(secp) 130 | excess_sig = bytearray([0] * 64) if self.excess_signature is None \ 131 | else self.excess_signature.to_bytearray(secp, True) 132 | 133 | return { 134 | "features": self.features if short else {"bits": self.features}, 135 | "fee": self.fee, 136 | "lock_height": self.lock_height, 137 | "excess": hexlify(excess).decode() if short else list(excess), 138 | "excess_sig": hexlify(excess_sig).decode() if short else list(excess_sig) 139 | } 140 | 141 | def verify(self, secp: Secp256k1) -> bool: 142 | return aggsig.verify(secp, self.excess_signature, self.excess.to_public_key(secp), self.fee, self.lock_height) 143 | 144 | @staticmethod 145 | def from_dict(secp: Secp256k1, dct: dict): 146 | return Kernel( 147 | dct['features']['bits'], 148 | dct['fee'], 149 | dct['lock_height'], 150 | None if sum(dct['excess']) == 0 else Commitment.from_bytearray(secp, bytearray(dct['excess'])), 151 | None if sum(dct['excess_sig']) == 0 else Signature.from_bytearray(secp, bytearray(dct['excess_sig']), True) 152 | ) 153 | 154 | 155 | class Transaction: 156 | def __init__(self, inputs: List[Input], outputs: List[Output], kernels: List[Kernel], offset: BlindingFactor): 157 | self.inputs = inputs 158 | self.outputs = outputs 159 | self.kernels = kernels 160 | self.offset = offset 161 | 162 | def to_dict(self, secp: Secp256k1, short=False) -> dict: 163 | return { 164 | "offset": self.offset.to_hex().decode() if short else list(self.offset.to_bytearray()), 165 | "body": { 166 | "inputs": [x.to_dict(secp, short) for x in self.inputs], 167 | "outputs": [x.to_dict(secp, short) for x in self.outputs], 168 | "kernels": [x.to_dict(secp, short) for x in self.kernels] 169 | } 170 | } 171 | 172 | def to_bytearray(self, secp: Secp256k1) -> bytearray: 173 | data = bytearray() 174 | data.extend(self.offset.to_bytearray()) 175 | data.extend(bytearray(len(self.inputs).to_bytes(8, "big"))) 176 | data.extend(bytearray(len(self.outputs).to_bytes(8, "big"))) 177 | data.extend(bytearray(len(self.kernels).to_bytes(8, "big"))) 178 | 179 | inputs = sort_by_hash(self.inputs, secp) 180 | for input in inputs: 181 | data.extend(input.to_bytearray(secp)) 182 | outputs = sort_by_hash(self.outputs, secp) 183 | for output in outputs: 184 | data.extend(output.to_bytearray(secp)) 185 | kernels = sort_by_hash(self.kernels, secp) 186 | for kernel in kernels: 187 | data.extend(kernel.to_bytearray(secp)) 188 | return data 189 | 190 | def to_hex(self, secp: Secp256k1): 191 | return hexlify(self.to_bytearray(secp)) 192 | 193 | def add_input(self, secp: Secp256k1, input: Input): 194 | self.inputs.append(input) 195 | self.inputs = sort_by_hash(self.inputs, secp) 196 | 197 | def add_output(self, secp: Secp256k1, output: Output): 198 | self.outputs.append(output) 199 | self.outputs = sort_by_hash(self.outputs, secp) 200 | 201 | def add_kernel(self, secp: Secp256k1, kernel: Kernel): 202 | self.kernels.append(kernel) 203 | self.kernels = sort_by_hash(self.kernels, secp) 204 | 205 | def sum_commitments(self, secp: Secp256k1) -> Commitment: 206 | overage = sum([x.fee for x in self.kernels]) 207 | inputs = [x.commit for x in self.inputs] 208 | outputs = [x.commit for x in self.outputs] 209 | if overage > 0: 210 | outputs.append(secp.commit_value(overage)) 211 | inputs.append(secp.commit(0, self.offset.to_secret_key(secp))) 212 | return secp.commit_sum(outputs, inputs) 213 | 214 | def verify_kernels(self, secp: Secp256k1) -> bool: 215 | for kernel in self.kernels: 216 | if not kernel.verify(secp): 217 | return False 218 | return True 219 | 220 | @staticmethod 221 | def empty(secp: Secp256k1, features: int, fee: int, lock_height: int): 222 | kernel = Kernel(features, fee, lock_height, None, None) 223 | return Transaction([], [], [kernel], BlindingFactor.from_secret_key(SecretKey.random(secp))) 224 | 225 | @staticmethod 226 | def from_dict(secp: Secp256k1, dct: dict): 227 | inputs = [] 228 | for input in dct['body']['inputs']: 229 | inputs.append(Input.from_dict(secp, input)) 230 | outputs = [] 231 | for output in dct['body']['outputs']: 232 | outputs.append(Output.from_dict(secp, output)) 233 | kernels = [] 234 | for kernel in dct['body']['kernels']: 235 | kernels.append(Kernel.from_dict(secp, kernel)) 236 | offset = BlindingFactor.from_bytearray(bytearray(dct['offset'])) 237 | return Transaction(inputs, outputs, kernels, offset) 238 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /secp256k1/pedersen.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from secp256k1 import Secp256k1 as Secp256k1_base, Message, SECRET_KEY_SIZE 3 | from secp256k1.key import SecretKey, PublicKey 4 | from ._libsecp256k1 import ffi, lib 5 | 6 | PEDERSEN_COMMITMENT_SIZE = 33 7 | MAX_PROOF_SIZE = 675 8 | PROOF_MSG_SIZE = 64 9 | MAX_WIDTH = 1 << 20 10 | 11 | 12 | # Pedersen Commitment xG+vH 13 | class Commitment: 14 | def __init__(self, secp): 15 | assert isinstance(secp, Secp256k1) 16 | self.commitment = ffi.new("secp256k1_pedersen_commitment *") 17 | self.secp = secp 18 | 19 | def __eq__(self, other): 20 | return isinstance(other, Commitment) and self.to_bytearray(self.secp) == other.to_bytearray(other.secp) 21 | 22 | def __str__(self): 23 | return "Commitment<{}>".format(self.to_hex(self.secp).decode()) 24 | 25 | def __repr__(self): 26 | return self.__str__() 27 | 28 | def to_bytearray(self, secp) -> bytearray: 29 | assert isinstance(secp, Secp256k1) 30 | out = ffi.new("char [%d]" % PEDERSEN_COMMITMENT_SIZE) 31 | res = lib.secp256k1_pedersen_commitment_serialize(secp.ctx, out, self.commitment) 32 | assert res, "Unable to serialize" 33 | return bytearray(ffi.buffer(out, PEDERSEN_COMMITMENT_SIZE)) 34 | 35 | def to_hex(self, secp) -> bytes: 36 | return hexlify(self.to_bytearray(secp)) 37 | 38 | def to_public_key(self, secp) -> PublicKey: 39 | assert isinstance(secp, Secp256k1) 40 | obj = PublicKey(secp) 41 | res = lib.secp256k1_pedersen_commitment_to_pubkey(secp.ctx, obj.key, self.commitment) 42 | assert res, "Unable to convert to public key" 43 | return obj 44 | 45 | @staticmethod 46 | def from_bytearray(secp, data: bytearray): 47 | assert isinstance(secp, Secp256k1) 48 | input = bytearray([0] * PEDERSEN_COMMITMENT_SIZE) 49 | for i in range(min(len(data), PEDERSEN_COMMITMENT_SIZE)): 50 | input[i] = data[i] 51 | obj = Commitment(secp) 52 | res = lib.secp256k1_pedersen_commitment_parse(secp.ctx, obj.commitment, bytes(input)) 53 | assert res, "Invalid commitment" 54 | return obj 55 | 56 | @staticmethod 57 | def from_hex(secp, data: bytes): 58 | return Commitment.from_bytearray(secp, bytearray(unhexlify(data))) 59 | 60 | 61 | class RangeProof: 62 | def __init__(self, proof: bytearray): 63 | self.proof = proof 64 | self.proof_len = len(proof) 65 | 66 | def __eq__(self, other): 67 | return isinstance(other, RangeProof) and self.proof == other.proof 68 | 69 | def __str__(self): 70 | return "RangeProof".format(self.proof_len, hexlify(self.proof[0:8]).decode()) 71 | 72 | def __repr__(self): 73 | return self.__str__() 74 | 75 | def to_bytearray(self) -> bytearray: 76 | return self.proof[:] 77 | 78 | def to_hex(self) -> bytes: 79 | return hexlify(bytes(self.proof)) 80 | 81 | @staticmethod 82 | def from_bytearray(data: bytearray): 83 | assert len(data) <= MAX_PROOF_SIZE, "Invalid proof size" 84 | return RangeProof(data) 85 | 86 | @staticmethod 87 | def from_hex(data: bytes): 88 | return RangeProof.from_bytearray(bytearray(unhexlify(data))) 89 | 90 | 91 | class Secp256k1(Secp256k1_base): 92 | def __init__(self, ctx, flags): 93 | super().__init__(ctx, flags) 94 | self.GENERATOR_G = ffi.new("secp256k1_generator *", [bytes([ 95 | 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 96 | 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 97 | 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 98 | 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, 99 | 0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 100 | 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, 0x11, 0x08, 0xa8, 101 | 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19, 102 | 0x9c, 0x47, 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8 103 | ])]) 104 | self.GENERATOR_H = ffi.new("secp256k1_generator *", [bytes([ 105 | 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 106 | 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, 107 | 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 108 | 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0, 109 | 0x31, 0xd3, 0xc6, 0x86, 0x39, 0x73, 0x92, 0x6e, 110 | 0x04, 0x9e, 0x63, 0x7c, 0xb1, 0xb5, 0xf4, 0x0a, 111 | 0x36, 0xda, 0xc2, 0x8a, 0xf1, 0x76, 0x69, 0x68, 112 | 0xc3, 0x0c, 0x23, 0x13, 0xf3, 0xa3, 0x89, 0x04 113 | ])]) 114 | 115 | self.gens = lib.secp256k1_bulletproof_generators_create(self.ctx, self.GENERATOR_G, 256) 116 | 117 | def commit(self, value: int, blind) -> Commitment: 118 | obj = Commitment(self) 119 | res = lib.secp256k1_pedersen_commit(self.ctx, obj.commitment, bytes(blind.key), value, 120 | self.GENERATOR_H, self.GENERATOR_G) 121 | assert res, "Unable to commit" 122 | return obj 123 | 124 | def commit_value(self, value: int) -> Commitment: 125 | blind = SecretKey() 126 | return self.commit(value, blind) 127 | 128 | def commit_sum(self, positives, negatives) -> Commitment: 129 | pos = [] 130 | for positive in positives: 131 | assert isinstance(positive, Commitment) 132 | pos.append(positive.commitment) 133 | neg = [] 134 | for negative in negatives: 135 | assert isinstance(negative, Commitment) 136 | neg.append(negative.commitment) 137 | commit_sum = Commitment(self) 138 | res = lib.secp256k1_pedersen_commit_sum(self.ctx, commit_sum.commitment, pos, len(pos), neg, len(neg)) 139 | assert res, "Unable to sum commitments" 140 | return commit_sum 141 | 142 | def blind_sum(self, positives, negatives) -> SecretKey: 143 | keys = [] 144 | for positive in positives: 145 | assert isinstance(positive, SecretKey) 146 | keys.append(ffi.new("char []", bytes(positive.key))) 147 | for negative in negatives: 148 | assert isinstance(negative, SecretKey) 149 | keys.append(ffi.new("char []", bytes(negative.key))) 150 | sum_key = ffi.new("char []", SECRET_KEY_SIZE) 151 | ret = lib.secp256k1_pedersen_blind_sum(self.ctx, sum_key, keys, len(keys), len(positives)) 152 | assert ret, "Unable to sum blinding factors" 153 | return SecretKey.from_bytearray(self, bytearray(ffi.buffer(sum_key, SECRET_KEY_SIZE))) 154 | 155 | def sign(self, secret_key: SecretKey, message: bytearray): 156 | assert len(message) == 32, "Invalid message length" 157 | signature_obj = ffi.new("secp256k1_ecdsa_signature *") 158 | res = lib.secp256k1_ecdsa_sign( 159 | self.ctx, signature_obj, bytes(message), bytes(secret_key.key), ffi.NULL, ffi.NULL 160 | ) 161 | assert res, "Unable to generate signature" 162 | signature_ptr = ffi.new("char []", 80) 163 | signature_len_ptr = ffi.new("size_t *", 80) 164 | res = lib.secp256k1_ecdsa_signature_serialize_der( 165 | self.ctx, signature_ptr, signature_len_ptr, signature_obj 166 | ) 167 | assert res, "Unable to DER serialize signature" 168 | return bytearray(ffi.buffer(signature_ptr, signature_len_ptr[0])) 169 | 170 | def sign_recoverable(self, secret_key: SecretKey, message: bytearray) -> bytearray: 171 | assert len(message) == 32, "Invalid message length" 172 | signature_obj = ffi.new("secp256k1_ecdsa_recoverable_signature *") 173 | res = lib.secp256k1_ecdsa_sign_recoverable( 174 | self.ctx, signature_obj, bytes(message), bytes(secret_key.key), ffi.NULL, ffi.NULL 175 | ) 176 | assert res, "Unable to generate recoverable signature" 177 | signature_ptr = ffi.new("char []", 64) 178 | rec_id_ptr = ffi.new("int *") 179 | res = lib.secp256k1_ecdsa_recoverable_signature_serialize_compact( 180 | self.ctx, signature_ptr, rec_id_ptr, signature_obj 181 | ) 182 | assert res, "Unable to serialize recoverable signature" 183 | signature = bytearray(ffi.buffer(signature_ptr, 64)) 184 | signature.append(rec_id_ptr[0]) 185 | return signature 186 | 187 | def bullet_proof(self, value: int, blind: SecretKey, nonce: SecretKey, extra_data: bytearray) -> RangeProof: 188 | proof_ptr = ffi.new("char []", MAX_PROOF_SIZE) 189 | proof_len_ptr = ffi.new("size_t *", MAX_PROOF_SIZE) 190 | blind_key = ffi.new("char []", bytes(blind.key)) 191 | scratch = lib.secp256k1_scratch_space_create(self.ctx, 256 * MAX_WIDTH) 192 | res = lib.secp256k1_bulletproof_rangeproof_prove( 193 | self.ctx, scratch, self.gens, proof_ptr, proof_len_ptr, ffi.NULL, ffi.NULL, ffi.NULL, 194 | [value], ffi.NULL, [blind_key], ffi.NULL, 1, self.GENERATOR_H, 64, bytes(nonce.key), ffi.NULL, 195 | bytes(extra_data), len(extra_data), ffi.NULL 196 | ) 197 | obj = RangeProof.from_bytearray(bytearray(ffi.buffer(proof_ptr, proof_len_ptr[0]))) 198 | lib.secp256k1_scratch_space_destroy(scratch) 199 | assert res, "Unable to generate bulletproof" 200 | return obj 201 | 202 | def bullet_proof_multisig_1(self, value: int, blind: SecretKey, commit: Commitment, common_nonce: SecretKey, 203 | nonce: SecretKey, extra_data: bytearray) -> (PublicKey, PublicKey): 204 | scratch = lib.secp256k1_scratch_space_create(self.ctx, 256 * MAX_WIDTH) 205 | t_1 = PublicKey(self) 206 | t_2 = PublicKey(self) 207 | blind_key = ffi.new("char []", bytes(blind.key)) 208 | res = lib.secp256k1_bulletproof_rangeproof_prove( 209 | self.ctx, scratch, self.gens, ffi.NULL, ffi.NULL, ffi.NULL, t_1.key, t_2.key, [value], 210 | ffi.NULL, [blind_key], [commit.commitment], 1, self.GENERATOR_H, 64, bytes(common_nonce.key), 211 | bytes(nonce.key), bytes(extra_data), len(extra_data), ffi.NULL 212 | ) 213 | lib.secp256k1_scratch_space_destroy(scratch) 214 | assert res, "Unable to generate multisig bulletproof" 215 | return t_1, t_2 216 | 217 | def bullet_proof_multisig_2(self, value: int, blind: SecretKey, commit: Commitment, common_nonce: SecretKey, 218 | nonce: SecretKey, t_1: PublicKey, t_2: PublicKey, extra_data: bytearray) -> SecretKey: 219 | scratch = lib.secp256k1_scratch_space_create(self.ctx, 256 * MAX_WIDTH) 220 | tau_x_ptr = ffi.new("char []", 32) 221 | blind_key = ffi.new("char []", bytes(blind.key)) 222 | res = lib.secp256k1_bulletproof_rangeproof_prove( 223 | self.ctx, scratch, self.gens, ffi.NULL, ffi.NULL, tau_x_ptr, t_1.key, t_2.key, [value], 224 | ffi.NULL, [blind_key], [commit.commitment], 1, self.GENERATOR_H, 64, bytes(common_nonce.key), 225 | bytes(nonce.key), bytes(extra_data), len(extra_data), ffi.NULL 226 | ) 227 | lib.secp256k1_scratch_space_destroy(scratch) 228 | assert res, "Unable to generate multisig bulletproof" 229 | return SecretKey.from_bytearray(self, bytearray(ffi.buffer(tau_x_ptr, 32))) 230 | 231 | def bullet_proof_multisig_3(self, value: int, blind: SecretKey, commit: Commitment, common_nonce: SecretKey, 232 | nonce: SecretKey, t_1: PublicKey, t_2: PublicKey, tau_x: SecretKey, 233 | extra_data: bytearray) -> RangeProof: 234 | scratch = lib.secp256k1_scratch_space_create(self.ctx, 256 * MAX_WIDTH) 235 | proof_ptr = ffi.new("char []", MAX_PROOF_SIZE) 236 | proof_len_ptr = ffi.new("size_t *", MAX_PROOF_SIZE) 237 | tau_x_ptr = ffi.new("char []", bytes(tau_x.to_bytearray())) 238 | blind_key = ffi.new("char []", bytes(blind.key)) 239 | res = lib.secp256k1_bulletproof_rangeproof_prove( 240 | self.ctx, scratch, self.gens, proof_ptr, proof_len_ptr, tau_x_ptr, t_1.key, t_2.key, 241 | [value], ffi.NULL, [blind_key], [commit.commitment], 1, self.GENERATOR_H, 64, bytes(common_nonce.key), 242 | bytes(nonce.key), bytes(extra_data), len(extra_data), ffi.NULL 243 | ) 244 | obj = RangeProof.from_bytearray(bytearray(ffi.buffer(proof_ptr, proof_len_ptr[0]))) 245 | lib.secp256k1_scratch_space_destroy(scratch) 246 | assert res, "Unable to generate multisig bulletproof" 247 | return obj 248 | 249 | def verify_bullet_proof(self, commit: Commitment, proof: RangeProof, extra_data: bytearray) -> bool: 250 | scratch = lib.secp256k1_scratch_space_create(self.ctx, 256 * MAX_WIDTH) 251 | res = lib.secp256k1_bulletproof_rangeproof_verify( 252 | self.ctx, scratch, self.gens, bytes(proof.proof), proof.proof_len, ffi.NULL, commit.commitment, 253 | 1, 64, self.GENERATOR_H, bytes(extra_data), len(extra_data) 254 | ) 255 | lib.secp256k1_scratch_space_destroy(scratch) 256 | return res == 1 257 | 258 | 259 | def ethereum_signature(data: bytearray) -> (bytes, bytes, int): 260 | assert len(data) == 65 261 | r = b"0x"+hexlify(bytes(data[:32])) 262 | s = b"0x"+hexlify(bytes(data[32:64])) 263 | v = int.from_bytes(bytes(data[64:]), "big") + 27 264 | return r, s, v 265 | -------------------------------------------------------------------------------- /grin/extkey.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import hashlib 3 | import hmac 4 | from typing import List 5 | from secp256k1 import Secp256k1 6 | from secp256k1.key import SecretKey, PublicKey 7 | from grin.util import base58check_encode, base58check_decode 8 | 9 | 10 | class HardenedIndexError(Exception): 11 | pass 12 | 13 | 14 | class ChildNumberRangeError(Exception): 15 | pass 16 | 17 | 18 | class ChainCode: 19 | def __init__(self, chain_code: bytearray): 20 | assert len(chain_code) == 32 21 | self.chain_code = chain_code 22 | 23 | def __eq__(self, other): 24 | return isinstance(other, ChainCode) and self.chain_code == other.chain_code 25 | 26 | def to_bytearray(self) -> bytearray: 27 | return self.chain_code[:] 28 | 29 | @staticmethod 30 | def from_bytearray(data: bytearray): 31 | return ChainCode(data) 32 | 33 | 34 | class Fingerprint: 35 | def __init__(self, data: bytearray): 36 | fingerprint = bytearray([0] * 4) 37 | for i in range(min(4, len(data))): 38 | fingerprint[i] = data[i] 39 | self.fingerprint = fingerprint 40 | 41 | def __eq__(self, other): 42 | return isinstance(other, Fingerprint) and self.fingerprint == other.fingerprint 43 | 44 | def to_bytearray(self) -> bytearray: 45 | return self.fingerprint[:] 46 | 47 | @staticmethod 48 | def from_bytearray(data: bytearray): 49 | return Fingerprint(data) 50 | 51 | @staticmethod 52 | def default(): 53 | return Fingerprint(bytearray([0] * 4)) 54 | 55 | 56 | class ChildNumber: 57 | def __init__(self, index: int, hardened: bool): 58 | if not 0 <= index < 2**31: 59 | raise ChildNumberRangeError() 60 | self.index = index 61 | self.hardened = hardened 62 | 63 | def __eq__(self, other): 64 | return isinstance(other, ChildNumber) and self.index == other.index and self.hardened == other.hardened 65 | 66 | def __repr__(self): 67 | return "ChildNumber<{}{}>".format(self.index, "h" if self.hardened else "") 68 | 69 | def to_index(self) -> int: 70 | index = self.index 71 | if self.hardened: 72 | index += 2 ** 31 73 | return index 74 | 75 | def to_bytearray(self) -> bytearray: 76 | return bytearray(self.to_index().to_bytes(4, "big")) 77 | 78 | def is_normal(self) -> bool: 79 | return not self.hardened 80 | 81 | def is_hardened(self) -> bool: 82 | return self.hardened 83 | 84 | @staticmethod 85 | def from_normal_index(index: int): 86 | return ChildNumber(index, False) 87 | 88 | @staticmethod 89 | def from_hardened_index(index: int): 90 | return ChildNumber(index, True) 91 | 92 | @staticmethod 93 | def from_index(index: int): 94 | if index >= 2**31: 95 | return ChildNumber(index-2**31, True) 96 | else: 97 | return ChildNumber(index, False) 98 | 99 | @staticmethod 100 | def from_bytearray(data: bytearray): 101 | return ChildNumber.from_index(int.from_bytes(bytes(data), "big")) 102 | 103 | 104 | class Hasher(abc.ABC): 105 | @abc.abstractmethod 106 | def network_secret(self) -> bytearray: 107 | pass 108 | 109 | @abc.abstractmethod 110 | def network_public(self) -> bytearray: 111 | pass 112 | 113 | @abc.abstractmethod 114 | def master_seed(self) -> bytearray: 115 | pass 116 | 117 | @abc.abstractmethod 118 | def init_sha512(self, seed: bytearray): 119 | pass 120 | 121 | @abc.abstractmethod 122 | def append_sha512(self, value: bytearray): 123 | pass 124 | 125 | @abc.abstractmethod 126 | def result_sha512(self) -> bytearray: 127 | pass 128 | 129 | @abc.abstractmethod 130 | def sha_256(self, input: bytearray) -> bytearray: 131 | pass 132 | 133 | @abc.abstractmethod 134 | def ripemd_160(self, input: bytearray) -> bytearray: 135 | pass 136 | 137 | 138 | class GrinHasher(Hasher): 139 | def __init__(self): 140 | self.hmac = hmac.new(bytearray([0]*128), None, hashlib.sha512) 141 | 142 | def network_secret(self) -> bytearray: 143 | return bytearray([0x03, 0x3C, 0x04, 0xA4]) 144 | 145 | def network_public(self) -> bytearray: 146 | return bytearray([0x03, 0x3C, 0x08, 0xDF]) 147 | 148 | def master_seed(self) -> bytearray: 149 | return bytearray(b"IamVoldemort") 150 | 151 | def init_sha512(self, seed: bytearray): 152 | self.hmac = hmac.new(seed, None, hashlib.sha512) 153 | 154 | def append_sha512(self, value: bytearray): 155 | self.hmac.update(bytes(value)) 156 | 157 | def result_sha512(self) -> bytearray: 158 | return bytearray(self.hmac.digest()) 159 | 160 | def sha_256(self, data: bytearray) -> bytearray: 161 | return bytearray(hashlib.sha256(bytes(data)).digest()) 162 | 163 | def ripemd_160(self, data: bytearray) -> bytearray: 164 | h = hashlib.new("ripemd160") 165 | h.update(bytes(data)) 166 | return bytearray(h.digest()) 167 | 168 | 169 | class ReferenceHasher(Hasher): 170 | def __init__(self): 171 | self.hmac = hmac.new(bytearray([0]*128), None, hashlib.sha512) 172 | 173 | def network_secret(self) -> bytearray: 174 | return bytearray([0x04, 0x88, 0xAD, 0xE4]) 175 | 176 | def network_public(self) -> bytearray: 177 | return bytearray([0x04, 0x88, 0xB2, 0x1E]) 178 | 179 | def master_seed(self) -> bytearray: 180 | return bytearray(b"Bitcoin seed") 181 | 182 | def init_sha512(self, seed: bytearray): 183 | self.hmac = hmac.new(seed, None, hashlib.sha512) 184 | 185 | def append_sha512(self, value: bytearray): 186 | self.hmac.update(bytes(value)) 187 | 188 | def result_sha512(self) -> bytearray: 189 | return bytearray(self.hmac.digest()) 190 | 191 | def sha_256(self, data: bytearray) -> bytearray: 192 | return bytearray(hashlib.sha256(bytes(data)).digest()) 193 | 194 | def ripemd_160(self, data: bytearray) -> bytearray: 195 | h = hashlib.new("ripemd160") 196 | h.update(bytes(data)) 197 | return bytearray(h.digest()) 198 | 199 | 200 | class ExtendedSecretKey: 201 | def __init__(self, network: bytearray, depth: int, parent_fingerprint: Fingerprint, child_number: ChildNumber, 202 | secret_key: SecretKey, chain_code: ChainCode): 203 | assert len(network) == 4 204 | assert 0 <= depth < 2**8 205 | self.network = network 206 | self.depth = depth 207 | self.parent_fingerprint = parent_fingerprint 208 | self.child_number = child_number 209 | self.secret_key = secret_key 210 | self.chain_code = chain_code 211 | 212 | def __eq__(self, other): 213 | return isinstance(other, ExtendedSecretKey) and self.network == other.network and self.depth == other.depth \ 214 | and self.parent_fingerprint == other.parent_fingerprint and self.child_number == other.child_number \ 215 | and self.secret_key == other.secret_key and self.chain_code == other.chain_code 216 | 217 | def to_bytearray(self) -> bytearray: 218 | data = bytearray() 219 | data.extend(self.network) 220 | data.extend(self.depth.to_bytes(1, "big")) 221 | data.extend(self.parent_fingerprint.to_bytearray()) 222 | data.extend(self.child_number.to_bytearray()) 223 | data.extend(self.chain_code.to_bytearray()) 224 | data.append(0) 225 | data.extend(self.secret_key.to_bytearray()) 226 | return data 227 | 228 | def to_base58check(self) -> bytes: 229 | return base58check_encode(self.to_bytearray()) 230 | 231 | def derive_secret(self, secp: Secp256k1, hasher: Hasher, path: List[ChildNumber]): 232 | key = self 233 | for i in path: 234 | key = key.ckd_secret(secp, hasher, i) 235 | return key 236 | 237 | def ckd_secret(self, secp: Secp256k1, hasher: Hasher, i: ChildNumber): 238 | hasher.init_sha512(self.chain_code.to_bytearray()) 239 | if i.is_normal(): 240 | hasher.append_sha512(self.secret_key.to_public_key(secp).to_bytearray(secp)) 241 | else: 242 | hasher.append_sha512(bytearray([0])) 243 | hasher.append_sha512(self.secret_key.to_bytearray()) 244 | hasher.append_sha512(i.to_bytearray()) 245 | hash = hasher.result_sha512() 246 | key = SecretKey.from_bytearray(secp, hash[:32]) 247 | key.add_assign(secp, self.secret_key) 248 | return ExtendedSecretKey(self.network, self.depth + 1, self.fingerprint(secp, hasher), 249 | i, key, ChainCode.from_bytearray(hash[32:])) 250 | 251 | def fingerprint(self, secp: Secp256k1, hasher: Hasher) -> Fingerprint: 252 | return Fingerprint.from_bytearray(self.identifier(secp, hasher)) 253 | 254 | def identifier(self, secp: Secp256k1, hasher: Hasher) -> bytearray: 255 | return hasher.ripemd_160(hasher.sha_256(self.secret_key.to_public_key(secp).to_bytearray(secp))) 256 | 257 | @staticmethod 258 | def new_master(secp: Secp256k1, hasher: Hasher, seed: bytearray): 259 | hasher.init_sha512(hasher.master_seed()) 260 | hasher.append_sha512(seed) 261 | hash = hasher.result_sha512() 262 | return ExtendedSecretKey(hasher.network_secret(), 0, Fingerprint.default(), ChildNumber.from_normal_index(0), 263 | SecretKey.from_bytearray(secp, hash[:32]), ChainCode.from_bytearray(hash[32:])) 264 | 265 | @staticmethod 266 | def from_bytearray(secp: Secp256k1, data: bytearray): 267 | assert len(data) == 78 268 | return ExtendedSecretKey(data[:4], data[4], Fingerprint.from_bytearray(data[5:9]), 269 | ChildNumber.from_bytearray(data[9:13]), SecretKey.from_bytearray(secp, data[46:78]), 270 | ChainCode.from_bytearray(data[13:45])) 271 | 272 | @staticmethod 273 | def from_base58check(secp: Secp256k1, data: bytes): 274 | return ExtendedSecretKey.from_bytearray(secp, base58check_decode(data)) 275 | 276 | 277 | class ExtendedPublicKey: 278 | def __init__(self, network: bytearray, depth: int, parent_fingerprint: Fingerprint, child_number: ChildNumber, 279 | public_key: PublicKey, chain_code: ChainCode): 280 | self.network = network 281 | self.depth = depth 282 | self.parent_fingerprint = parent_fingerprint 283 | self.child_number = child_number 284 | self.public_key = public_key 285 | self.chain_code = chain_code 286 | 287 | def __eq__(self, other): 288 | return isinstance(other, ExtendedPublicKey) and self.network == other.network and self.depth == other.depth \ 289 | and self.parent_fingerprint == other.parent_fingerprint and self.child_number == other.child_number \ 290 | and self.public_key == other.public_key and self.chain_code == other.chain_code 291 | 292 | def to_bytearray(self, secp: Secp256k1) -> bytearray: 293 | data = bytearray() 294 | data.extend(self.network) 295 | data.extend(self.depth.to_bytes(1, "big")) 296 | data.extend(self.parent_fingerprint.to_bytearray()) 297 | data.extend(self.child_number.to_bytearray()) 298 | data.extend(self.chain_code.to_bytearray()) 299 | data.extend(self.public_key.to_bytearray(secp)) 300 | return data 301 | 302 | def to_base58check(self, secp: Secp256k1) -> bytes: 303 | return base58check_encode(self.to_bytearray(secp)) 304 | 305 | def derive_public(self, secp: Secp256k1, hasher: Hasher, path: List[ChildNumber]): 306 | key = self 307 | for i in path: 308 | key = key.ckd_public(secp, hasher, i) 309 | return key 310 | 311 | def ckd_public(self, secp: Secp256k1, hasher: Hasher, i: ChildNumber): 312 | if i.is_hardened(): 313 | raise HardenedIndexError() 314 | 315 | hasher.init_sha512(self.chain_code.to_bytearray()) 316 | hasher.append_sha512(self.public_key.to_bytearray(secp)) 317 | hasher.append_sha512(i.to_bytearray()) 318 | hash = hasher.result_sha512() 319 | key = SecretKey.from_bytearray(secp, hash[:32]).to_public_key(secp) 320 | key.add_assign(secp, self.public_key) 321 | 322 | return ExtendedPublicKey(self.network, self.depth + 1, self.fingerprint(secp, hasher), 323 | i, key, ChainCode.from_bytearray(hash[32:])) 324 | 325 | def fingerprint(self, secp: Secp256k1, hasher: Hasher) -> Fingerprint: 326 | return Fingerprint.from_bytearray(self.identifier(secp, hasher)) 327 | 328 | def identifier(self, secp: Secp256k1, hasher: Hasher) -> bytearray: 329 | return hasher.ripemd_160(hasher.sha_256(self.public_key.to_bytearray(secp))) 330 | 331 | @staticmethod 332 | def from_secret(secp: Secp256k1, hasher: Hasher, key: ExtendedSecretKey): 333 | return ExtendedPublicKey(hasher.network_public(), key.depth, key.parent_fingerprint, key.child_number, 334 | key.secret_key.to_public_key(secp), key.chain_code) 335 | 336 | @staticmethod 337 | def from_bytearray(secp: Secp256k1, data: bytearray): 338 | assert len(data) == 78 339 | return ExtendedPublicKey(data[:4], data[4], Fingerprint.from_bytearray(data[5:9]), 340 | ChildNumber.from_bytearray(data[9:13]), PublicKey.from_bytearray(secp, data[45:78]), 341 | ChainCode.from_bytearray(data[13:45])) 342 | 343 | @staticmethod 344 | def from_base58check(secp: Secp256k1, data: bytes): 345 | return ExtendedPublicKey.from_bytearray(secp, base58check_decode(data)) 346 | -------------------------------------------------------------------------------- /examples/multisig_tx.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify, unhexlify 2 | from http.server import HTTPServer 3 | import json 4 | from time import time 5 | from urllib.request import urlopen 6 | from secp256k1 import FLAG_ALL 7 | from secp256k1.key import PublicKey, Signature 8 | from secp256k1.pedersen import Secp256k1, Commitment, RangeProof 9 | import grin.aggsig as aggsig 10 | from grin.keychain import BlindSum, SecretKey 11 | from grin.proof import MultiPartyBulletProof 12 | from grin.transaction import tx_fee, Transaction, Input, Output, OutputFeatures 13 | from grin.util import MILLI_GRIN_UNIT, GRIN_UNIT, HTTPServerHandler, set_callback_post 14 | from grin.wallet import Wallet 15 | 16 | secp = None 17 | wallet = None 18 | server = None 19 | proof_builder = None 20 | 21 | 22 | def send(node_url: str): 23 | global secp, wallet, proof_builder 24 | 25 | now = int(time()) 26 | 27 | send_amount = GRIN_UNIT 28 | lock_height = 1 29 | refund_lock_height = lock_height + 1440 # ~24 hours 30 | dest_url = "http://127.0.0.1:18185" 31 | fluff = True 32 | 33 | secp = Secp256k1(None, FLAG_ALL) 34 | wallet = Wallet.open(secp, "wallet_a") 35 | 36 | print("Preparing to create multisig with {}".format(dest_url)) 37 | 38 | input_entries = wallet.select_outputs(send_amount + tx_fee(1, 2, MILLI_GRIN_UNIT)) 39 | fee_amount = tx_fee(len(input_entries), 2, MILLI_GRIN_UNIT) 40 | input_amount = sum(x.value for x in input_entries) 41 | change_amount = input_amount - send_amount - fee_amount 42 | refund_fee_amount = tx_fee(1, 1, MILLI_GRIN_UNIT) 43 | 44 | print("Selected {} inputs".format(len(input_entries))) 45 | 46 | tx = Transaction.empty(secp, 0, fee_amount, lock_height) 47 | refund_tx = Transaction.empty(secp, 0, refund_fee_amount, refund_lock_height) 48 | 49 | blind_sum = BlindSum() 50 | 51 | # Inputs 52 | inputs = [] 53 | for entry in input_entries: 54 | entry.mark_locked() 55 | blind_sum.sub_child_key(wallet.derive_from_entry(entry)) 56 | input = wallet.entry_to_input(entry) 57 | tx.add_input(secp, input) 58 | inputs.append(input) 59 | 60 | # Change output 61 | change_child, change_entry = wallet.create_output(change_amount) 62 | blind_sum.add_child_key(change_child) 63 | change_output = wallet.entry_to_output(change_entry) 64 | tx.add_output(secp, change_output) 65 | 66 | # Multisig output 67 | partial_child, partial_entry = wallet.create_output(send_amount) 68 | partial_entry.mark_locked() 69 | blind_sum.add_child_key(partial_child) 70 | public_partial_commit = wallet.commit_with_child_key(0, partial_child) 71 | 72 | # Refund output 73 | refund_amount = send_amount-refund_fee_amount 74 | refund_child, refund_entry = wallet.create_output(refund_amount) 75 | refund_output = wallet.entry_to_output(refund_entry) 76 | refund_tx.add_output(secp, refund_output) 77 | 78 | # Offset 79 | blind_sum.sub_blinding_factor(tx.offset) 80 | 81 | # Excess 82 | excess = wallet.chain.blind_sum(blind_sum).to_secret_key(secp) 83 | public_excess = excess.to_public_key(secp) 84 | 85 | # Nonce 86 | nonce = SecretKey.random(secp) 87 | public_nonce = nonce.to_public_key(secp) 88 | 89 | # Refund nonce 90 | refund_nonce = SecretKey.random(secp) 91 | refund_public_nonce = refund_nonce.to_public_key(secp) 92 | 93 | dct = { 94 | "amount": send_amount, 95 | "fee": fee_amount, 96 | "refund_fee": refund_fee_amount, 97 | "lock_height": lock_height, 98 | "refund_lock_height": refund_lock_height, 99 | "public_partial_commit": public_partial_commit.to_hex(secp).decode(), 100 | "public_nonce": public_nonce.to_hex(secp).decode(), 101 | "refund_public_nonce": refund_public_nonce.to_hex(secp).decode() 102 | } 103 | 104 | f = open("logs/{}_multisig_1.json".format(now), "w") 105 | f.write(json.dumps(dct, indent=2)) 106 | f.close() 107 | 108 | print("Sending to receiver..") 109 | 110 | req = urlopen(dest_url, json.dumps(dct).encode(), 60) 111 | dct2 = json.loads(req.read().decode()) 112 | 113 | f = open("logs/{}_multisig_2.json".format(now), "w") 114 | f.write(json.dumps(dct2, indent=2)) 115 | f.close() 116 | 117 | print("Received response, processing..") 118 | 119 | public_partial_commit_recv = Commitment.from_hex(secp, dct2['public_partial_commit'].encode()) 120 | public_partial_recv = public_partial_commit_recv.to_public_key(secp) 121 | public_nonce_recv = PublicKey.from_hex(secp, dct2['public_nonce'].encode()) 122 | public_excess_recv = public_partial_commit_recv.to_public_key(secp) 123 | partial_signature_recv = Signature.from_hex(dct2['partial_signature'].encode()) 124 | refund_public_nonce_recv = PublicKey.from_hex(secp, dct2['refund_public_nonce'].encode()) 125 | refund_public_excess_recv = PublicKey.from_hex(secp, dct2['refund_public_excess'].encode()) 126 | refund_partial_signature_recv = Signature.from_hex(dct2['refund_partial_signature'].encode()) 127 | 128 | # Commitment 129 | commit = secp.commit_sum([public_partial_commit_recv, wallet.commit(partial_entry)], []) 130 | print("Total commit: {}".format(commit)) 131 | 132 | # Nonce sums 133 | public_nonce_sum = PublicKey.from_combination(secp, [public_nonce_recv, public_nonce]) 134 | refund_public_nonce_sum = PublicKey.from_combination(secp, [refund_public_nonce_recv, refund_public_nonce]) 135 | 136 | # Step 2 of bulletproof 137 | proof_builder = MultiPartyBulletProof(secp, partial_child, public_partial_recv, send_amount, commit) 138 | t_1_recv = PublicKey.from_hex(secp, dct2['t_1'].encode()) 139 | t_2_recv = PublicKey.from_hex(secp, dct2['t_2'].encode()) 140 | t_1, t_2 = proof_builder.step_1() 141 | proof_builder.fill_step_1(t_1_recv, t_2_recv) 142 | tau_x = proof_builder.step_2() 143 | 144 | dct3 = { 145 | "t_1": t_1.to_hex(secp).decode(), 146 | "t_2": t_2.to_hex(secp).decode(), 147 | "tau_x": tau_x.to_hex().decode() 148 | } 149 | 150 | f = open("logs/{}_multisig_3.json".format(now), "w") 151 | f.write(json.dumps(dct3, indent=2)) 152 | f.close() 153 | 154 | print("Sending bulletproof component..") 155 | 156 | req2 = urlopen(dest_url, json.dumps(dct3).encode(), 60) 157 | dct4 = json.loads(req2.read().decode()) 158 | 159 | print("Received response") 160 | 161 | f = open("logs/{}_multisig_4.json".format(now), "w") 162 | f.write(json.dumps(dct4, indent=2)) 163 | f.close() 164 | 165 | # Bulletproof 166 | proof = RangeProof.from_bytearray(bytearray(unhexlify(dct4['proof'].encode()))) 167 | output = Output(OutputFeatures.DEFAULT_OUTPUT, commit, proof) 168 | assert output.verify(secp), "Invalid bulletproof" 169 | tx.add_output(secp, output) 170 | print("Created bulletproof") 171 | 172 | # First we finalize the refund tx, and check its validity 173 | refund_input = Input(OutputFeatures.DEFAULT_OUTPUT, commit) 174 | refund_tx.add_input(secp, refund_input) 175 | 176 | # Refund excess 177 | refund_blind_sum = BlindSum() 178 | refund_blind_sum.sub_child_key(partial_child) 179 | refund_blind_sum.add_child_key(refund_child) 180 | refund_blind_sum.sub_blinding_factor(refund_tx.offset) 181 | refund_excess = wallet.chain.blind_sum(refund_blind_sum).to_secret_key(secp) 182 | refund_public_excess = refund_excess.to_public_key(secp) 183 | 184 | # Refund partial signature 185 | refund_partial_signature = aggsig.calculate_partial( 186 | secp, refund_excess, refund_nonce, refund_public_nonce_sum, refund_fee_amount, refund_lock_height 187 | ) 188 | 189 | # Refund final signature 190 | refund_public_excess_sum = PublicKey.from_combination(secp, [refund_public_excess_recv, refund_public_excess]) 191 | refund_signature = aggsig.add_partials(secp, [refund_partial_signature_recv, refund_partial_signature], 192 | refund_public_nonce_sum) 193 | assert aggsig.verify(secp, refund_signature, refund_public_excess_sum, refund_fee_amount, refund_lock_height), \ 194 | "Unable to verify refund signature" 195 | refund_kernel = refund_tx.kernels[0] 196 | refund_kernel.excess = refund_tx.sum_commitments(secp) 197 | refund_kernel.excess_signature = refund_signature 198 | assert refund_tx.verify_kernels(secp), "Unable to verify refund kernel" 199 | 200 | print("Refund tx is valid") 201 | 202 | f = open("logs/{}_refund.json".format(now), "w") 203 | f.write(json.dumps(refund_tx.to_dict(secp), indent=2)) 204 | f.close() 205 | 206 | refund_tx_wrapper = { 207 | "tx_hex": refund_tx.to_hex(secp).decode() 208 | } 209 | 210 | f = open("logs/{}_refund_hex.json".format(now), "w") 211 | f.write(json.dumps(refund_tx_wrapper, indent=2)) 212 | f.close() 213 | 214 | print("Finalizing multisig tx..") 215 | 216 | # Partial signature 217 | partial_signature = aggsig.calculate_partial(secp, excess, nonce, public_nonce_sum, fee_amount, lock_height) 218 | 219 | # Final signature 220 | public_excess_sum = PublicKey.from_combination(secp, [public_excess_recv, public_excess]) 221 | signature = aggsig.add_partials(secp, [partial_signature_recv, partial_signature], public_nonce_sum) 222 | assert aggsig.verify(secp, signature, public_excess_sum, fee_amount, lock_height), "Unable to verify signature" 223 | kernel = tx.kernels[0] 224 | kernel.excess = tx.sum_commitments(secp) 225 | kernel.excess_signature = signature 226 | assert tx.verify_kernels(secp), "Unable to verify kernel" 227 | 228 | f = open("logs/{}_tx.json".format(now), "w") 229 | f.write(json.dumps(tx.to_dict(secp), indent=2)) 230 | f.close() 231 | 232 | tx_wrapper = { 233 | "tx_hex": tx.to_hex(secp).decode() 234 | } 235 | 236 | f = open("logs/{}_tx_hex.json".format(now), "w") 237 | f.write(json.dumps(tx_wrapper, indent=2)) 238 | f.close() 239 | 240 | print("Submitting to node..") 241 | 242 | urlopen("{}/v1/pool/push".format(node_url) + ("?fluff" if fluff else ""), json.dumps(tx_wrapper).encode(), 600) 243 | 244 | wallet.save() 245 | 246 | print("Transaction complete!") 247 | 248 | 249 | def post(handler: HTTPServerHandler): 250 | global secp, wallet, server, proof_builder 251 | 252 | if secp is None: 253 | secp = Secp256k1(None, FLAG_ALL) 254 | wallet = Wallet.open(secp, "wallet_b") 255 | 256 | try: 257 | length = handler.headers['Content-Length'] 258 | length = 0 if length is None else int(length) 259 | if length == 0: 260 | raise Exception("Invalid length") 261 | dct = json.loads(handler.rfile.read(length).decode()) 262 | 263 | if proof_builder is not None: 264 | print("Creating bulletproof components") 265 | 266 | t_1_sender = PublicKey.from_hex(secp, dct['t_1'].encode()) 267 | t_2_sender = PublicKey.from_hex(secp, dct['t_2'].encode()) 268 | proof_builder.fill_step_1(t_1_sender, t_2_sender) 269 | tau_x_sender = SecretKey.from_hex(secp, dct['tau_x'].encode()) 270 | proof_builder.fill_step_2(tau_x_sender) 271 | proof = proof_builder.finalize() 272 | 273 | wallet.save() 274 | 275 | dct2 = { 276 | "proof": hexlify(bytes(proof.proof)).decode() 277 | } 278 | 279 | handler.json_response((json.dumps(dct2) + "\r\n").encode()) 280 | 281 | print("Sent response") 282 | 283 | return 284 | 285 | send_amount = dct['amount'] 286 | fee_amount = dct['fee'] 287 | refund_fee_amount = dct['refund_fee'] 288 | lock_height = dct['lock_height'] 289 | refund_lock_height = dct['refund_lock_height'] 290 | 291 | print("Receive {} grin in multisig".format(send_amount / GRIN_UNIT)) 292 | 293 | public_partial_commit_sender = Commitment.from_hex(secp, dct['public_partial_commit'].encode()) 294 | public_partial_sender = public_partial_commit_sender.to_public_key(secp) 295 | public_nonce_sender = PublicKey.from_hex(secp, dct['public_nonce'].encode()) 296 | refund_public_nonce_sender = PublicKey.from_hex(secp, dct['refund_public_nonce'].encode()) 297 | 298 | # Multisig output 299 | partial_child, partial_entry = wallet.create_output(send_amount) 300 | partial_entry.mark_locked() 301 | public_partial_commit = wallet.commit_with_child_key(0, partial_child) 302 | 303 | # Commitment 304 | commit = secp.commit_sum([public_partial_commit_sender, wallet.commit(partial_entry)], []) 305 | print("Total commit: {}".format(commit)) 306 | 307 | # Nonce 308 | nonce = SecretKey.random(secp) 309 | public_nonce = nonce.to_public_key(secp) 310 | public_nonce_sum = PublicKey.from_combination(secp, [public_nonce_sender, public_nonce]) 311 | 312 | # Refund excess 313 | refund_blind_sum = BlindSum() 314 | refund_blind_sum.sub_child_key(partial_child) 315 | refund_excess = wallet.chain.blind_sum(refund_blind_sum).to_secret_key(secp) 316 | refund_public_excess = refund_excess.to_public_key(secp) 317 | 318 | # Refund nonce 319 | refund_nonce = SecretKey.random(secp) 320 | refund_public_nonce = refund_nonce.to_public_key(secp) 321 | refund_public_nonce_sum = PublicKey.from_combination(secp, [refund_public_nonce_sender, refund_public_nonce]) 322 | 323 | # Start building the bulletproof for the multisig output 324 | proof_builder = MultiPartyBulletProof(secp, partial_child, public_partial_sender, send_amount, commit) 325 | t_1, t_2 = proof_builder.step_1() 326 | 327 | # Partial signature 328 | partial_signature = aggsig.calculate_partial( 329 | secp, partial_child.key, nonce, public_nonce_sum, fee_amount, lock_height 330 | ) 331 | 332 | # Refund partial signature 333 | refund_partial_signature = aggsig.calculate_partial( 334 | secp, refund_excess, refund_nonce, refund_public_nonce_sum, refund_fee_amount, refund_lock_height 335 | ) 336 | 337 | dct2 = { 338 | "public_partial_commit": public_partial_commit.to_hex(secp).decode(), 339 | "refund_public_excess": refund_public_excess.to_hex(secp).decode(), 340 | "public_nonce": public_nonce.to_hex(secp).decode(), 341 | "refund_public_nonce": refund_public_nonce.to_hex(secp).decode(), 342 | "partial_signature": partial_signature.to_hex().decode(), 343 | "refund_partial_signature": refund_partial_signature.to_hex().decode(), 344 | "t_1": t_1.to_hex(secp).decode(), 345 | "t_2": t_2.to_hex(secp).decode() 346 | } 347 | 348 | handler.json_response((json.dumps(dct2) + "\r\n").encode()) 349 | 350 | print("Sent response") 351 | 352 | except Exception as e: 353 | print("Unable to parse input: {}".format(e)) 354 | handler.error_response() 355 | 356 | 357 | def receive(): 358 | global secp, wallet, server 359 | secp = Secp256k1(None, FLAG_ALL) 360 | wallet = Wallet.open(secp, "wallet_2") 361 | 362 | set_callback_post(post) 363 | 364 | print("Listening on port 18185..") 365 | server = HTTPServer(("", 18185), HTTPServerHandler) 366 | try: 367 | server.serve_forever() 368 | except KeyboardInterrupt: 369 | server.socket.close() 370 | -------------------------------------------------------------------------------- /examples/swap.py: -------------------------------------------------------------------------------- 1 | import json 2 | import math 3 | import os 4 | from time import time 5 | from secp256k1 import FLAG_ALL 6 | from secp256k1.pedersen import Secp256k1, ethereum_signature 7 | from grin.btc import TXID, OutputPoint, Address as BitcoinAddress 8 | from grin.swap import AtomicSwap, Role, Stage, is_eth_address, is_btc_address, is_btc_txid 9 | from grin.util import GRIN_UNIT, UUID, absolute 10 | from grin.wallet import Wallet, NotEnoughFundsException 11 | 12 | ETHER_UNIT = 1000000000000000000 13 | BITCOIN_UNIT = 100000000 14 | 15 | if not os.path.isdir(absolute("swap_data")): 16 | os.mkdir(absolute("swap_data")) 17 | f = open(absolute("swap_data", "_DONT_PUBLISH_THESE_FILES_"), "w") 18 | f.write("Seriously, dont") 19 | f.close() 20 | if not os.path.isdir(absolute("swap_data", "sell")): 21 | os.mkdir(absolute("swap_data", "sell")) 22 | if not os.path.isdir(absolute("swap_data", "buy")): 23 | os.mkdir(absolute("swap_data", "buy")) 24 | 25 | 26 | def sell(file=None): 27 | assert file is None or isinstance(file, str), "Invalid argument" 28 | 29 | secp = Secp256k1(None, FLAG_ALL) 30 | 31 | print("################################") 32 | 33 | swap = None 34 | if file is None: 35 | print("# Grin -> BTC/ETH atomic swap\n# ") 36 | 37 | id = UUID.random() 38 | swap = AtomicSwap(secp, Role.SELLER, id) 39 | 40 | print("# This script is used for selling grin coins for Bitcoin or Ether through an atomic swap\n" 41 | "# ") 42 | 43 | wallet_dir = input("# What is the name of the wallet you want to use? ") 44 | if not os.path.isdir(wallet_dir): 45 | print("# Wallet directory not found") 46 | return 47 | swap.wallet = Wallet.open(secp, wallet_dir) 48 | 49 | grin_amount = float(input("# How much grin do you want to sell? ")) 50 | assert grin_amount > 0 and math.isfinite(grin_amount), "Invalid input" 51 | swap.grin_amount = int(grin_amount*GRIN_UNIT) 52 | 53 | currency = input("# Which currency would you like to receive? [BTC/ETH]: ") 54 | if currency != "BTC" and currency != "ETH": 55 | print("# Unknown currency, please enter BTC or ETH") 56 | return 57 | swap.swap_currency = currency 58 | 59 | swap_amount = float(input("# How much {} do you want to receive? ".format(currency))) 60 | assert swap_amount > 0 and math.isfinite(swap_amount), "Invalid input" 61 | if swap.is_bitcoin_swap(): 62 | swap.swap_amount = int(swap_amount * BITCOIN_UNIT) 63 | swap_receive_address = input("# At which Bitcoin address would you like to receive this? ") 64 | assert is_btc_address(swap_receive_address, False), "Invalid input" 65 | elif swap.is_ether_swap(): 66 | swap.swap_amount = int(swap_amount * ETHER_UNIT) 67 | swap_receive_address = input("# At which Ethereum address would you like to receive this? ") 68 | assert is_eth_address(swap_receive_address), "Invalid input" 69 | else: 70 | print("# Unknown swap currency") 71 | return 72 | 73 | swap.swap_receive_address = swap_receive_address 74 | 75 | last_block = float(input("# What is the height of the last Grin T4 block? ")) 76 | assert last_block > 0 and math.isfinite(last_block), "Invalid input" 77 | swap.lock_height = int(last_block) 78 | swap.refund_lock_height = swap.lock_height+720 # ~12h 79 | 80 | try: 81 | swap.select_inputs() 82 | except NotEnoughFundsException: 83 | print("# Not enough funds available!") 84 | return 85 | else: 86 | f = open(absolute(file), "r") 87 | dct = json.loads(f.read()) 88 | f.close() 89 | 90 | if dct['target'] != "seller": 91 | print("# This file is not meant for me") 92 | return 93 | 94 | id = UUID.from_str(dct['id']) 95 | swap_file = absolute("swap_data", "sell", "{}.json".format(str(id))) 96 | if not os.path.exists(swap_file): 97 | print("# Swap file not found") 98 | return 99 | swap = AtomicSwap(secp, Role.SELLER, id) 100 | 101 | print("# Grin -> {} atomic swap\n# ".format(swap.swap_currency)) 102 | 103 | if dct['stage'] != swap.stage.value: 104 | print("# Unexpected stage") 105 | return 106 | 107 | diff = int((time() - swap.time_start) / 60) 108 | diff_hour = int(abs(diff) / 60) 109 | diff_min = int(abs(diff) % 60) 110 | if diff_hour >= 6: 111 | print("# WARNING: this swap was initiated {}h{}m ago".format(diff_hour, diff_min)) 112 | if input("# Do you want to continue? [Y/n]: ") not in ["", "Y", "y"]: 113 | return 114 | print("# ") 115 | 116 | swap.receive(dct) 117 | 118 | if swap.stage == Stage.SIGN: 119 | if swap.is_bitcoin_swap(): 120 | print("# Check that the balance of address {} is at least {} tBTC, with enough confirmations".format( 121 | swap.btc_lock_address.to_base58check().decode(), swap.swap_amount / BITCOIN_UNIT 122 | )) 123 | 124 | if input("# Is this the case? [Y/n]: ") not in ["", "Y", "y"]: 125 | print("# Please rerun this script when the address has enough balance") 126 | return 127 | 128 | utxo_count = int(input("# How many UTXOs does the address have? ")) 129 | assert utxo_count > 0 and math.isfinite(utxo_count), "Invalid input" 130 | print("# Output point format: TXID:index") 131 | swap.btc_output_points = [] 132 | for i in range(utxo_count): 133 | output_point = input("# Insert output point for UTXO #{}: ".format(i)) 134 | split = output_point.split(":") 135 | assert len(split) == 2, "Invalid input" 136 | output_txid = split[0] 137 | assert is_btc_txid(output_txid), "Invalid output point TXID" 138 | output_index = int(split[1]) 139 | assert output_index >= 0 and math.isfinite(output_index), "Invalid output point index" 140 | swap.btc_output_points.append(OutputPoint(TXID.from_hex(output_txid.encode()), output_index)) 141 | 142 | if swap.is_ether_swap(): 143 | print("# Check the ETH Ropsten contract at {} for\n" 144 | "# balance of at least {} ETH\n" 145 | "# sign_address = {}\n" 146 | "# receive_address = {}\n" 147 | "# unlock_time far enough in the future (>18h)" 148 | "# enough confirmations".format(swap.eth_contract_address, swap.swap_amount / ETHER_UNIT, 149 | swap.eth_address_lock, swap.swap_receive_address)) 150 | 151 | if input("# Does the contract fulfil these requirements? [Y/n]: ") not in ["", "Y", "y"]: 152 | print("# The buyer tried to scam you, but we caught it in time!") 153 | return 154 | 155 | swap.fill_signatures() 156 | elif swap.stage == Stage.LOCK: 157 | swap.build_transactions() 158 | 159 | name = "{}_multisig_tx.json".format(swap.short_id()) 160 | tx_wrapper = { 161 | "tx_hex": swap.tx.to_hex(secp).decode() 162 | } 163 | f = open(name, "w") 164 | f.write(json.dumps(tx_wrapper, indent=2)) 165 | f.close() 166 | 167 | print("# Transaction written to {}, please submit it to a node".format(name)) 168 | 169 | tx_height = float(input("# Enter the height of the block containing the transaction: ")) 170 | assert tx_height > 0 and math.isfinite(tx_height), "Invalid input" 171 | swap.tx_height = int(tx_height) 172 | 173 | refund_name = "{}_refund_tx_lock{}.json".format(swap.short_id(), swap.refund_lock_height) 174 | refund_tx_wrapper = { 175 | "tx_hex": swap.refund_tx.to_hex(secp).decode() 176 | } 177 | f = open(refund_name, "w") 178 | f.write(json.dumps(refund_tx_wrapper, indent=2)) 179 | f.close() 180 | 181 | print("# Refund transaction written to {}, can be submitted at height {}".format(refund_name, 182 | swap.refund_lock_height)) 183 | elif swap.stage == Stage.SWAP: 184 | swap.fill_swap_signatures() 185 | elif swap.stage == Stage.DONE: 186 | swap.finalize_swap() 187 | 188 | if swap.is_bitcoin_swap(): 189 | btc_swap_name = "{}_btc_swap_tx.hex".format(swap.short_id()) 190 | f = open(btc_swap_name, "w") 191 | f.write(swap.claim.decode()) 192 | f.close() 193 | 194 | print("# The buyer has claimed their Grin!\n" 195 | "# \n" 196 | "# Bitcoin transaction written to {}, submit it to a node\n" 197 | "# This will give you the testnet BTC and complete the swap. Congratulations!\n" 198 | "################################".format(btc_swap_name)) 199 | 200 | if swap.is_ether_swap(): 201 | r, s, v = ethereum_signature(swap.claim) 202 | print("# The buyer has claimed their Grin!\n" 203 | "# \n" 204 | "# Submit a transaction to contract {}, 'claim' method, with the following arguments:\n" 205 | "# r = {}\n" 206 | "# s = {}\n" 207 | "# v = {}\n" 208 | "# This will give you the Ropsten ETH and complete the swap. Congratulations!\n" 209 | "################################".format(swap.eth_contract_address, r.decode(), s.decode(), v)) 210 | 211 | return 212 | 213 | swap.save() 214 | 215 | out_name = "{}_seller_{}.json".format(swap.short_id(), swap.stage.value) 216 | f = open(absolute(out_name), "x") 217 | f.write(json.dumps(swap.send(), indent=2)) 218 | f.close() 219 | 220 | print("# \n" 221 | "# Created file '{}', please send it to the buyer\n" 222 | "################################".format(out_name)) 223 | 224 | 225 | def buy(file=None): 226 | assert file is None or isinstance(file, str), "Invalid argument" 227 | 228 | secp = Secp256k1(None, FLAG_ALL) 229 | 230 | print("################################") 231 | 232 | if file is None: 233 | print("# BTC/ETH -> grin atomic swap\n# ") 234 | print("# This script is used for buying grin coins with Bitcoin or Ether through an atomic swap\n" 235 | "# The seller initiates the process, wait for them to send you a file and run './swap buy ") 236 | return 237 | 238 | f = open(absolute(file), "r") 239 | dct = json.loads(f.read()) 240 | f.close() 241 | 242 | if dct['target'] != "buyer": 243 | print("# This file is not meant for me") 244 | return 245 | 246 | id = UUID.from_str(dct['id']) 247 | swap_file = absolute("swap_data", "buy", "{}.json".format(str(id))) 248 | 249 | swap = AtomicSwap(secp, Role.BUYER, id) 250 | 251 | if not os.path.exists(swap_file): 252 | if dct['stage'] != Stage.INIT.value: 253 | print("# Unexpected transaction stage") 254 | return 255 | 256 | if dct['swap_currency'] != "BTC" and dct['swap_currency'] != "ETH": 257 | print("# Unsupported currency {}".format(dct['swap_currency'])) 258 | return 259 | 260 | print("# {} -> grin atomic swap\n# ".format(dct['swap_currency'])) 261 | 262 | normalize_unit = BITCOIN_UNIT if dct['swap_currency'] == "BTC" else ETHER_UNIT 263 | 264 | print("# You are about to start the process of buying {} grin for {} {}".format( 265 | dct['grin_amount'] / GRIN_UNIT, dct['swap_amount'] / normalize_unit, dct['swap_currency'])) 266 | 267 | if input("# Are you sure? [Y/n]: ") not in ["", "Y", "y"]: 268 | print("# You declined the offer") 269 | return 270 | 271 | wallet_dir = input("# What is the name of the wallet you want to use? ") 272 | if not os.path.isdir(wallet_dir): 273 | print("# Wallet directory not found") 274 | return 275 | dct['wallet'] = wallet_dir 276 | 277 | swap.receive(dct) 278 | 279 | last_block = float(input("# What is the height of the last Grin T4 block? ")) 280 | assert last_block > 0 and math.isfinite(last_block), "Invalid input" 281 | 282 | print("# ") 283 | diff = swap.lock_height-last_block 284 | diff_hour = int(abs(diff) / 60) 285 | diff_min = int(abs(diff) % 60) 286 | if diff >= 10 or diff <= -60: 287 | print("#\n" 288 | "# WARNING: transaction unlocks ~{}h{}m minutes in the {} (height {})\n" 289 | "# ".format(diff_hour, diff_min, "future" if diff > 0 else "past", swap.lock_height)) 290 | 291 | diff_refund = swap.refund_lock_height-last_block 292 | diff_refund_hour = int(diff_refund / 60) 293 | diff_refund_min = int(diff_refund % 60) 294 | if diff_refund < 360: 295 | print("# Refund unlocks less than 6 hours from now (~{}h{}m, height {})".format( 296 | diff_refund_hour, diff_refund_min, swap.refund_lock_height)) 297 | return 298 | 299 | print("# Refund unlocks in ~{}h{}m (height {})\n" 300 | "# ".format(diff_refund_hour, diff_refund_min, swap.refund_lock_height)) 301 | 302 | if swap.is_bitcoin_swap(): 303 | print("# Please send (at least) {} tBTC to {}".format(swap.swap_amount / BITCOIN_UNIT, 304 | swap.btc_lock_address.to_base58check().decode())) 305 | input("# When you are done, press Enter: ") 306 | 307 | if swap.is_ether_swap(): 308 | print( 309 | "# Please deploy the GrinSwap.sol contract on the Ethereum Ropsten testnet" 310 | "with the following arguments:\n" 311 | "# _sign_address = {}\n" 312 | "# _receive_address = {}\n" 313 | "# and deposit (at least) {} ETH in it".format(swap.eth_address_lock, swap.swap_receive_address, 314 | swap.swap_amount / ETHER_UNIT)) 315 | 316 | eth_contract_address = input("# When you are done, enter the contract address: ") 317 | assert is_eth_address(eth_contract_address), "Invalid input" 318 | swap.eth_contract_address = eth_contract_address 319 | 320 | swap.fill_signatures() 321 | else: 322 | print("# {} -> grin atomic swap\n# ".format(swap.swap_currency)) 323 | 324 | if dct['stage'] != swap.stage.value+1: 325 | print("# Unexpected stage") 326 | return 327 | 328 | diff = int((time() - swap.time_start) / 60) 329 | diff_hour = int(abs(diff) / 60) 330 | diff_min = int(abs(diff) % 60) 331 | if diff_hour >= 6: 332 | print("# WARNING: this swap was initiated {}h{}m ago".format(diff_hour, diff_min)) 333 | if input("# Do you want to continue? [Y/n]: ") not in ["", "Y", "y"]: 334 | return 335 | print("# ") 336 | 337 | swap.receive(dct) 338 | 339 | if swap.stage == Stage.SIGN: 340 | swap.finalize_range_proof() 341 | elif swap.stage == Stage.LOCK: 342 | print("# The seller claims the multisig output {} was mined in block {}".format( 343 | swap.commit.to_hex(secp).decode(), swap.tx_height)) 344 | if input("# Does this block (or another) contain the output? [Y/n]: ") not in ["", "Y", "y"]: 345 | print("# You can rerun this command when the output has been mined") 346 | return 347 | swap.prepare_swap() 348 | elif swap.stage == Stage.SWAP: 349 | swap.finalize_swap() 350 | 351 | swap_name = "{}_swap_tx.json".format(swap.short_id()) 352 | swap_tx_wrapper = { 353 | "tx_hex": swap.swap_tx.to_hex(secp).decode() 354 | } 355 | f = open(swap_name, "w") 356 | f.write(json.dumps(swap_tx_wrapper, indent=2)) 357 | f.close() 358 | 359 | print("# Transaction written to {}, please submit it to a node to claim your Grin".format(swap_name)) 360 | input("# Press [Enter] when the transaction has been mined: ") 361 | print("# \n" 362 | "# Congratulations, you completed the swap!") 363 | else: 364 | print("# Not sure what to do") 365 | return 366 | 367 | swap.save() 368 | 369 | out_name = "{}_buyer_{}.json".format(swap.short_id(), swap.stage.value) 370 | f = open(absolute(out_name), "x") 371 | f.write(json.dumps(swap.send(), indent=2)) 372 | f.close() 373 | 374 | print("# \n" 375 | "# Created file '{}', please send it to the seller\n" 376 | "################################".format(out_name)) 377 | --------------------------------------------------------------------------------