commit
8a3f626801
126 changed files with 32858 additions and 2494 deletions
@ -1,60 +0,0 @@ |
||||
dist: bionic |
||||
os: linux |
||||
language: python |
||||
python: '3.7' |
||||
stages: |
||||
- lint |
||||
- test |
||||
env: |
||||
global: |
||||
- TEST_DIR=${HOME}/test_basicswap2 |
||||
- TEST_RELOAD_PATH=~/test_basicswap1 |
||||
- BIN_DIR=~/cached_bin |
||||
- PARTICL_BINDIR=${BIN_DIR}/particl |
||||
- BITCOIN_BINDIR=${BIN_DIR}/bitcoin |
||||
- LITECOIN_BINDIR=${BIN_DIR}/litecoin |
||||
- XMR_BINDIR=${BIN_DIR}/monero |
||||
cache: |
||||
directories: |
||||
- "$BIN_DIR" |
||||
before_install: |
||||
- sudo apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config |
||||
install: |
||||
- travis_retry pip install tox pytest |
||||
before_script: |
||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip |
||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip |
||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true |
||||
- cd coincurve-anonswap |
||||
- python3 setup.py install --force |
||||
script: |
||||
- cd $TRAVIS_BUILD_DIR |
||||
- python3 setup.py install |
||||
- basicswap-prepare --bindir=${BIN_DIR} --preparebinonly --withcoins=particl,bitcoin,litecoin,monero |
||||
- export DATADIRS="${TEST_DIR}" |
||||
- mkdir -p "${DATADIRS}/bin" |
||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin" |
||||
- mkdir -p "${TEST_RELOAD_PATH}/bin" |
||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin" |
||||
- # tox |
||||
- pytest tests/basicswap/test_xmr.py |
||||
- pytest tests/basicswap/test_xmr_reload.py |
||||
- pytest tests/basicswap/test_xmr_bids_offline.py |
||||
after_success: |
||||
- echo "End test" |
||||
jobs: |
||||
include: |
||||
- stage: lint |
||||
env: |
||||
cache: false |
||||
install: |
||||
- travis_retry pip install flake8==3.7.0 |
||||
- travis_retry pip install codespell==1.15.0 |
||||
before_script: |
||||
script: |
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py |
||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static |
||||
after_success: |
||||
- echo "End lint" |
||||
- stage: test |
||||
env: |
@ -1,3 +1,3 @@ |
||||
name = "basicswap" |
||||
|
||||
__version__ = "0.13.0" |
||||
__version__ = "0.13.4" |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@ |
||||
|
@ -0,0 +1,533 @@ |
||||
|
||||
|
||||
intro = """ |
||||
blake.py |
||||
version 5, 2-Apr-2014 |
||||
|
||||
BLAKE is a SHA3 round-3 finalist designed and submitted by |
||||
Jean-Philippe Aumasson et al. |
||||
|
||||
At the core of BLAKE is a ChaCha-like mixer, very similar |
||||
to that found in the stream cipher, ChaCha8. Besides being |
||||
a very good mixer, ChaCha is fast. |
||||
|
||||
References: |
||||
http://www.131002.net/blake/ |
||||
http://csrc.nist.gov/groups/ST/hash/sha-3/index.html |
||||
http://en.wikipedia.org/wiki/BLAKE_(hash_function) |
||||
|
||||
This implementation assumes all data is in increments of |
||||
whole bytes. (The formal definition of BLAKE allows for |
||||
hashing individual bits.) Note too that this implementation |
||||
does include the round-3 tweaks where the number of rounds |
||||
was increased to 14/16 from 10/14. |
||||
|
||||
This version can be imported into both Python2 (2.6 and 2.7) |
||||
and Python3 programs. Python 2.5 requires an older version |
||||
of blake.py (version 4). |
||||
|
||||
Here are some comparative times for different versions of |
||||
Python: |
||||
|
||||
64-bit: |
||||
2.6 6.284s |
||||
2.7 6.343s |
||||
3.2 7.620s |
||||
pypy (2.7) 2.080s |
||||
|
||||
32-bit: |
||||
2.5 (32) 15.389s (with psyco) |
||||
2.7-32 13.645s |
||||
3.2-32 12.574s |
||||
|
||||
One test on a 2.0GHz Core 2 Duo of 10,000 iterations of |
||||
BLAKE-256 on a short message produced a time of 5.7 seconds. |
||||
Not bad, but if raw speed is what you want, look to the |
||||
the C version. It is 40x faster and did the same thing |
||||
in 0.13 seconds. |
||||
|
||||
Copyright (c) 2009-2012 by Larry Bugbee, Kent, WA |
||||
ALL RIGHTS RESERVED. |
||||
|
||||
blake.py IS EXPERIMENTAL SOFTWARE FOR EDUCATIONAL |
||||
PURPOSES ONLY. IT IS MADE AVAILABLE "AS-IS" WITHOUT |
||||
WARRANTY OR GUARANTEE OF ANY KIND. USE SIGNIFIES |
||||
ACCEPTANCE OF ALL RISK. |
||||
|
||||
To make your learning and experimentation less cumbersome, |
||||
blake.py is free for any use. |
||||
|
||||
Enjoy, |
||||
|
||||
Larry Bugbee |
||||
March 2011 |
||||
rev May 2011 - fixed Python version check (tx JP) |
||||
rev Apr 2012 - fixed an out-of-order bit set in final() |
||||
- moved self-test to a separate test pgm |
||||
- this now works with Python2 and Python3 |
||||
rev Apr 2014 - added test and conversion of string input |
||||
to byte string in update() (tx Soham) |
||||
- added hexdigest() method. |
||||
- now support state 3 so only one call to |
||||
final() per instantiation is allowed. all |
||||
subsequent calls to final(), digest() or |
||||
hexdigest() simply return the stored value. |
||||
|
||||
""" |
||||
|
||||
import struct |
||||
from binascii import hexlify, unhexlify |
||||
|
||||
#--------------------------------------------------------------- |
||||
|
||||
class BLAKE(object): |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
# initial values, constants and padding |
||||
|
||||
# IVx for BLAKE-x |
||||
|
||||
IV64 = [ |
||||
0x6A09E667F3BCC908, 0xBB67AE8584CAA73B, |
||||
0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1, |
||||
0x510E527FADE682D1, 0x9B05688C2B3E6C1F, |
||||
0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179, |
||||
] |
||||
|
||||
IV48 = [ |
||||
0xCBBB9D5DC1059ED8, 0x629A292A367CD507, |
||||
0x9159015A3070DD17, 0x152FECD8F70E5939, |
||||
0x67332667FFC00B31, 0x8EB44A8768581511, |
||||
0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4, |
||||
] |
||||
|
||||
# note: the values here are the same as the high-order |
||||
# half-words of IV64 |
||||
IV32 = [ |
||||
0x6A09E667, 0xBB67AE85, |
||||
0x3C6EF372, 0xA54FF53A, |
||||
0x510E527F, 0x9B05688C, |
||||
0x1F83D9AB, 0x5BE0CD19, |
||||
] |
||||
|
||||
# note: the values here are the same as the low-order |
||||
# half-words of IV48 |
||||
IV28 = [ |
||||
0xC1059ED8, 0x367CD507, |
||||
0x3070DD17, 0xF70E5939, |
||||
0xFFC00B31, 0x68581511, |
||||
0x64F98FA7, 0xBEFA4FA4, |
||||
] |
||||
|
||||
# constants for BLAKE-64 and BLAKE-48 |
||||
C64 = [ |
||||
0x243F6A8885A308D3, 0x13198A2E03707344, |
||||
0xA4093822299F31D0, 0x082EFA98EC4E6C89, |
||||
0x452821E638D01377, 0xBE5466CF34E90C6C, |
||||
0xC0AC29B7C97C50DD, 0x3F84D5B5B5470917, |
||||
0x9216D5D98979FB1B, 0xD1310BA698DFB5AC, |
||||
0x2FFD72DBD01ADFB7, 0xB8E1AFED6A267E96, |
||||
0xBA7C9045F12C7F99, 0x24A19947B3916CF7, |
||||
0x0801F2E2858EFC16, 0x636920D871574E69, |
||||
] |
||||
|
||||
# constants for BLAKE-32 and BLAKE-28 |
||||
# note: concatenate and the values are the same as the values |
||||
# for the 1st half of C64 |
||||
C32 = [ |
||||
0x243F6A88, 0x85A308D3, |
||||
0x13198A2E, 0x03707344, |
||||
0xA4093822, 0x299F31D0, |
||||
0x082EFA98, 0xEC4E6C89, |
||||
0x452821E6, 0x38D01377, |
||||
0xBE5466CF, 0x34E90C6C, |
||||
0xC0AC29B7, 0xC97C50DD, |
||||
0x3F84D5B5, 0xB5470917, |
||||
] |
||||
|
||||
# the 10 permutations of:0,...15} |
||||
SIGMA = [ |
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15], |
||||
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3], |
||||
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4], |
||||
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8], |
||||
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13], |
||||
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9], |
||||
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11], |
||||
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10], |
||||
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5], |
||||
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0], |
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15], |
||||
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3], |
||||
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4], |
||||
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8], |
||||
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13], |
||||
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9], |
||||
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11], |
||||
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10], |
||||
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5], |
||||
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0], |
||||
] |
||||
|
||||
MASK32BITS = 0xFFFFFFFF |
||||
MASK64BITS = 0xFFFFFFFFFFFFFFFF |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def __init__(self, hashbitlen): |
||||
""" |
||||
load the hashSate structure (copy hashbitlen...) |
||||
hashbitlen: length of the hash output |
||||
""" |
||||
if hashbitlen not in [224, 256, 384, 512]: |
||||
raise Exception('hash length not 224, 256, 384 or 512') |
||||
|
||||
self.hashbitlen = hashbitlen |
||||
self.h = [0]*8 # current chain value (initialized to the IV) |
||||
self.t = 0 # number of *BITS* hashed so far |
||||
self.cache = b'' # cached leftover data not yet compressed |
||||
self.salt = [0]*4 # salt (null by default) |
||||
self.state = 1 # set to 2 by update and 3 by final |
||||
self.nullt = 0 # Boolean value for special case \ell_i=0 |
||||
|
||||
# The algorithm is the same for both the 32- and 64- versions |
||||
# of BLAKE. The difference is in word size (4 vs 8 bytes), |
||||
# blocksize (64 vs 128 bytes), number of rounds (14 vs 16) |
||||
# and a few very specific constants. |
||||
if (hashbitlen == 224) or (hashbitlen == 256): |
||||
# setup for 32-bit words and 64-bit block |
||||
self.byte2int = self._fourByte2int |
||||
self.int2byte = self._int2fourByte |
||||
self.MASK = self.MASK32BITS |
||||
self.WORDBYTES = 4 |
||||
self.WORDBITS = 32 |
||||
self.BLKBYTES = 64 |
||||
self.BLKBITS = 512 |
||||
self.ROUNDS = 14 # was 10 before round 3 |
||||
self.cxx = self.C32 |
||||
self.rot1 = 16 # num bits to shift in G |
||||
self.rot2 = 12 # num bits to shift in G |
||||
self.rot3 = 8 # num bits to shift in G |
||||
self.rot4 = 7 # num bits to shift in G |
||||
self.mul = 0 # for 32-bit words, 32<<self.mul where self.mul = 0 |
||||
|
||||
# 224- and 256-bit versions (32-bit words) |
||||
if hashbitlen == 224: |
||||
self.h = self.IV28[:] |
||||
else: |
||||
self.h = self.IV32[:] |
||||
|
||||
elif (hashbitlen == 384) or (hashbitlen == 512): |
||||
# setup for 64-bit words and 128-bit block |
||||
self.byte2int = self._eightByte2int |
||||
self.int2byte = self._int2eightByte |
||||
self.MASK = self.MASK64BITS |
||||
self.WORDBYTES = 8 |
||||
self.WORDBITS = 64 |
||||
self.BLKBYTES = 128 |
||||
self.BLKBITS = 1024 |
||||
self.ROUNDS = 16 # was 14 before round 3 |
||||
self.cxx = self.C64 |
||||
self.rot1 = 32 # num bits to shift in G |
||||
self.rot2 = 25 # num bits to shift in G |
||||
self.rot3 = 16 # num bits to shift in G |
||||
self.rot4 = 11 # num bits to shift in G |
||||
self.mul = 1 # for 64-bit words, 32<<self.mul where self.mul = 1 |
||||
|
||||
# 384- and 512-bit versions (64-bit words) |
||||
if hashbitlen == 384: |
||||
self.h = self.IV48[:] |
||||
else: |
||||
self.h = self.IV64[:] |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def _compress(self, block): |
||||
byte2int = self.byte2int |
||||
mul = self.mul # de-reference these for ...speed? ;-) |
||||
cxx = self.cxx |
||||
rot1 = self.rot1 |
||||
rot2 = self.rot2 |
||||
rot3 = self.rot3 |
||||
rot4 = self.rot4 |
||||
MASK = self.MASK |
||||
WORDBITS = self.WORDBITS |
||||
SIGMA = self.SIGMA |
||||
|
||||
# get message (<<2 is the same as *4 but faster) |
||||
m = [byte2int(block[i<<2<<mul:(i<<2<<mul)+(4<<mul)]) for i in range(16)] |
||||
|
||||
# initialization |
||||
v = [0]*16 |
||||
v[ 0: 8] = [self.h[i] for i in range(8)] |
||||
v[ 8:16] = [self.cxx[i] for i in range(8)] |
||||
v[ 8:12] = [v[8+i] ^ self.salt[i] for i in range(4)] |
||||
if self.nullt == 0: # (i>>1 is the same as i/2 but faster) |
||||
v[12] = v[12] ^ (self.t & MASK) |
||||
v[13] = v[13] ^ (self.t & MASK) |
||||
v[14] = v[14] ^ (self.t >> self.WORDBITS) |
||||
v[15] = v[15] ^ (self.t >> self.WORDBITS) |
||||
|
||||
# - - - - - - - - - - - - - - - - - |
||||
# ready? let's ChaCha!!! |
||||
|
||||
def G(a, b, c, d, i): |
||||
va = v[a] # it's faster to deref and reref later |
||||
vb = v[b] |
||||
vc = v[c] |
||||
vd = v[d] |
||||
|
||||
sri = SIGMA[round][i] |
||||
sri1 = SIGMA[round][i+1] |
||||
|
||||
va = ((va + vb) + (m[sri] ^ cxx[sri1]) ) & MASK |
||||
x = vd ^ va |
||||
vd = (x >> rot1) | ((x << (WORDBITS-rot1)) & MASK) |
||||
vc = (vc + vd) & MASK |
||||
x = vb ^ vc |
||||
vb = (x >> rot2) | ((x << (WORDBITS-rot2)) & MASK) |
||||
|
||||
va = ((va + vb) + (m[sri1] ^ cxx[sri]) ) & MASK |
||||
x = vd ^ va |
||||
vd = (x >> rot3) | ((x << (WORDBITS-rot3)) & MASK) |
||||
vc = (vc + vd) & MASK |
||||
x = vb ^ vc |
||||
vb = (x >> rot4) | ((x << (WORDBITS-rot4)) & MASK) |
||||
|
||||
v[a] = va |
||||
v[b] = vb |
||||
v[c] = vc |
||||
v[d] = vd |
||||
|
||||
for round in range(self.ROUNDS): |
||||
# column step |
||||
G( 0, 4, 8,12, 0) |
||||
G( 1, 5, 9,13, 2) |
||||
G( 2, 6,10,14, 4) |
||||
G( 3, 7,11,15, 6) |
||||
# diagonal step |
||||
G( 0, 5,10,15, 8) |
||||
G( 1, 6,11,12,10) |
||||
G( 2, 7, 8,13,12) |
||||
G( 3, 4, 9,14,14) |
||||
|
||||
# - - - - - - - - - - - - - - - - - |
||||
|
||||
# save current hash value (use i&0x3 to get 0,1,2,3,0,1,2,3) |
||||
self.h = [self.h[i]^v[i]^v[i+8]^self.salt[i&0x3] |
||||
for i in range(8)] |
||||
# print 'self.h', [num2hex(h) for h in self.h] |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def addsalt(self, salt): |
||||
""" adds a salt to the hash function (OPTIONAL) |
||||
should be called AFTER Init, and BEFORE update |
||||
salt: a bytestring, length determined by hashbitlen. |
||||
if not of sufficient length, the bytestring |
||||
will be assumed to be a big endian number and |
||||
prefixed with an appropriate number of null |
||||
bytes, and if too large, only the low order |
||||
bytes will be used. |
||||
|
||||
if hashbitlen=224 or 256, then salt will be 16 bytes |
||||
if hashbitlen=384 or 512, then salt will be 32 bytes |
||||
""" |
||||
# fail if addsalt() was not called at the right time |
||||
if self.state != 1: |
||||
raise Exception('addsalt() not called after init() and before update()') |
||||
# salt size is to be 4x word size |
||||
saltsize = self.WORDBYTES * 4 |
||||
# if too short, prefix with null bytes. if too long, |
||||
# truncate high order bytes |
||||
if len(salt) < saltsize: |
||||
salt = (chr(0)*(saltsize-len(salt)) + salt) |
||||
else: |
||||
salt = salt[-saltsize:] |
||||
# prep the salt array |
||||
self.salt[0] = self.byte2int(salt[ : 4<<self.mul]) |
||||
self.salt[1] = self.byte2int(salt[ 4<<self.mul: 8<<self.mul]) |
||||
self.salt[2] = self.byte2int(salt[ 8<<self.mul:12<<self.mul]) |
||||
self.salt[3] = self.byte2int(salt[12<<self.mul: ]) |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def update(self, data): |
||||
""" update the state with new data, storing excess data |
||||
as necessary. may be called multiple times and if a |
||||
call sends less than a full block in size, the leftover |
||||
is cached and will be consumed in the next call |
||||
data: data to be hashed (bytestring) |
||||
""" |
||||
self.state = 2 |
||||
|
||||
BLKBYTES = self.BLKBYTES # de-referenced for improved readability |
||||
BLKBITS = self.BLKBITS |
||||
|
||||
datalen = len(data) |
||||
if not datalen: return |
||||
|
||||
if type(data) == type(u''): |
||||
|
||||
# use either of the next two lines for a proper |
||||
# response under both Python2 and Python3 |
||||
data = data.encode('UTF-8') # converts to byte string |
||||
#data = bytearray(data, 'utf-8') # use if want mutable |
||||
|
||||
# This next line works for Py3 but fails under |
||||
# Py2 because the Py2 version of bytes() will |
||||
# accept only *one* argument. Arrrrgh!!! |
||||
#data = bytes(data, 'utf-8') # converts to immutable byte |
||||
# string but... under p7 |
||||
# bytes() wants only 1 arg |
||||
# ...a dummy, 2nd argument like encoding=None |
||||
# that does nothing would at least allow |
||||
# compatibility between Python2 and Python3. |
||||
|
||||
left = len(self.cache) |
||||
fill = BLKBYTES - left |
||||
|
||||
# if any cached data and any added new data will fill a |
||||
# full block, fill and compress |
||||
if left and datalen >= fill: |
||||
self.cache = self.cache + data[:fill] |
||||
self.t += BLKBITS # update counter |
||||
self._compress(self.cache) |
||||
self.cache = b'' |
||||
data = data[fill:] |
||||
datalen -= fill |
||||
|
||||
# compress new data until not enough for a full block |
||||
while datalen >= BLKBYTES: |
||||
self.t += BLKBITS # update counter |
||||
self._compress(data[:BLKBYTES]) |
||||
data = data[BLKBYTES:] |
||||
datalen -= BLKBYTES |
||||
|
||||
# cache all leftover bytes until next call to update() |
||||
if datalen > 0: |
||||
self.cache = self.cache + data[:datalen] |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def final(self, data=''): |
||||
""" finalize the hash -- pad and hash remaining data |
||||
returns hashval, the digest |
||||
""" |
||||
if self.state == 3: |
||||
# we have already finalized so simply return the |
||||
# previously calculated/stored hash value |
||||
return self.hash |
||||
|
||||
if data: |
||||
self.update(data) |
||||
|
||||
ZZ = b'\x00' |
||||
ZO = b'\x01' |
||||
OZ = b'\x80' |
||||
OO = b'\x81' |
||||
PADDING = OZ + ZZ*128 # pre-formatted padding data |
||||
|
||||
# copy nb. bits hash in total as a 64-bit BE word |
||||
# copy nb. bits hash in total as a 128-bit BE word |
||||
tt = self.t + (len(self.cache) << 3) |
||||
if self.BLKBYTES == 64: |
||||
msglen = self._int2eightByte(tt) |
||||
else: |
||||
low = tt & self.MASK |
||||
high = tt >> self.WORDBITS |
||||
msglen = self._int2eightByte(high) + self._int2eightByte(low) |
||||
|
||||
# size of block without the words at the end that count |
||||
# the number of bits, 55 or 111. |
||||
# Note: (((self.WORDBITS/8)*2)+1) equals ((self.WORDBITS>>2)+1) |
||||
sizewithout = self.BLKBYTES - ((self.WORDBITS>>2)+1) |
||||
|
||||
if len(self.cache) == sizewithout: |
||||
# special case of one padding byte |
||||
self.t -= 8 |
||||
if self.hashbitlen in [224, 384]: |
||||
self.update(OZ) |
||||
else: |
||||
self.update(OO) |
||||
else: |
||||
if len(self.cache) < sizewithout: |
||||
# enough space to fill the block |
||||
# use t=0 if no remaining data |
||||
if len(self.cache) == 0: |
||||
self.nullt=1 |
||||
self.t -= (sizewithout - len(self.cache)) << 3 |
||||
self.update(PADDING[:sizewithout - len(self.cache)]) |
||||
else: |
||||
# NOT enough space, need 2 compressions |
||||
# ...add marker, pad with nulls and compress |
||||
self.t -= (self.BLKBYTES - len(self.cache)) << 3 |
||||
self.update(PADDING[:self.BLKBYTES - len(self.cache)]) |
||||
# ...now pad w/nulls leaving space for marker & bit count |
||||
self.t -= (sizewithout+1) << 3 |
||||
self.update(PADDING[1:sizewithout+1]) # pad with zeroes |
||||
|
||||
self.nullt = 1 # raise flag to set t=0 at the next _compress |
||||
|
||||
# append a marker byte |
||||
if self.hashbitlen in [224, 384]: |
||||
self.update(ZZ) |
||||
else: |
||||
self.update(ZO) |
||||
self.t -= 8 |
||||
|
||||
# append the number of bits (long long) |
||||
self.t -= self.BLKBYTES |
||||
self.update(msglen) |
||||
|
||||
hashval = [] |
||||
if self.BLKBYTES == 64: |
||||
for h in self.h: |
||||
hashval.append(self._int2fourByte(h)) |
||||
else: |
||||
for h in self.h: |
||||
hashval.append(self._int2eightByte(h)) |
||||
|
||||
self.hash = b''.join(hashval)[:self.hashbitlen >> 3] |
||||
self.state = 3 |
||||
|
||||
return self.hash |
||||
|
||||
digest = final # may use digest() as a synonym for final() |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
|
||||
def hexdigest(self, data=''): |
||||
return hexlify(self.final(data)).decode('UTF-8') |
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||
# utility functions |
||||
|
||||
def _fourByte2int(self, bytestr): # see also long2byt() below |
||||
""" convert a 4-byte string to an int (long) """ |
||||
return struct.unpack('!L', bytestr)[0] |
||||
|
||||
def _eightByte2int(self, bytestr): |
||||
""" convert a 8-byte string to an int (long long) """ |
||||
return struct.unpack('!Q', bytestr)[0] |
||||
|
||||
def _int2fourByte(self, x): # see also long2byt() below |
||||
""" convert a number to a 4-byte string, high order |
||||
truncation possible (in Python x could be a BIGNUM) |
||||
""" |
||||
return struct.pack('!L', x) |
||||
|
||||
def _int2eightByte(self, x): |
||||
""" convert a number to a 8-byte string, high order |
||||
truncation possible (in Python x could be a BIGNUM) |
||||
""" |
||||
return struct.pack('!Q', x) |
||||
|
||||
|
||||
#--------------------------------------------------------------- |
||||
#--------------------------------------------------------------- |
||||
#--------------------------------------------------------------- |
||||
|
||||
|
||||
def blake_hash(data): |
||||
return BLAKE(256).digest(data) |
@ -0,0 +1,37 @@ |
||||
from blake256 import blake_hash |
||||
|
||||
testVectors = [ |
||||
["716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""], |
||||
["43234ff894a9c0590d0246cfc574eb781a80958b01d7a2fa1ac73c673ba5e311", "a"], |
||||
["658c6d9019a1deddbcb3640a066dfd23471553a307ab941fd3e677ba887be329", "ab"], |
||||
["1833a9fa7cf4086bd5fda73da32e5a1d75b4c3f89d5c436369f9d78bb2da5c28", "abc"], |
||||
["35282468f3b93c5aaca6408582fced36e578f67671ed0741c332d68ac72d7aa2", "abcd"], |
||||
["9278d633efce801c6aa62987d7483d50e3c918caed7d46679551eed91fba8904", "abcde"], |
||||
["7a17ee5e289845adcafaf6ca1b05c4a281b232a71c7083f66c19ba1d1169a8d4", "abcdef"], |
||||
["ee8c7f94ff805cb2e644643010ea43b0222056420917ec70c3da764175193f8f", "abcdefg"], |
||||
["7b37c0876d29c5add7800a1823795a82b809fc12f799ff6a4b5e58d52c42b17e", "abcdefgh"], |
||||
["bdc514bea74ffbb9c3aa6470b08ceb80a88e313ad65e4a01457bbffd0acc86de", "abcdefghi"], |
||||
["12e3afb9739df8d727e93d853faeafc374cc55aedc937e5a1e66f5843b1d4c2e", "abcdefghij"], |
||||
["22297d373b751f581944bb26315133f6fda2f0bf60f65db773900f61f81b7e79", "Discard medicine more than two years old."], |
||||
["4d48d137bc9cf6d21415b805bf33f59320337d85c673998260e03a02a0d760cd", "He who has a shady past knows that nice guys finish last."], |
||||
["beba299e10f93e17d45663a6dc4b8c9349e4f5b9bac0d7832389c40a1b401e5c", "I wouldn't marry him with a ten foot pole."], |
||||
["42e082ae7f967781c6cd4e0ceeaeeb19fb2955adbdbaf8c7ec4613ac130071b3", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"], |
||||
["207d06b205bfb359df91b48b6fd8aa6e4798b712d1cc5e91a254da9cef8684a3", "The days of the digital watch are numbered. -Tom Stoppard"], |
||||
["d56eab6927e371e2148b0788779aaf565d30567af2af822b6be3b90db9767a70", "Nepal premier won't resign."], |
||||
["01020709ca7fd10dc7756ce767d508d7206167d300b7a7ed76838a8547a7898c", "For every action there is an equal and opposite government program."], |
||||
["5569a6cc6535a66da221d8f6ad25008f28752d0343f3f1d757f1ecc9b1c61536", "His money is twice tainted: 'taint yours and 'taint mine."], |
||||
["8ff699b5ac7687c82600e89d0ff6cfa87e7179759184386971feb76fbae9975f", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"], |
||||
["f4b3a7c85a418b15ce330fd41ae0254b036ad48dd98aa37f0506a995ba9c6029", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"], |
||||
["1ed94bab64fe560ef0983165fcb067e9a8a971c1db8e6fb151ff9a7c7fe877e3", "size: a.out: bad magic"], |
||||
["ff15b54992eedf9889f7b4bbb16692881aa01ed10dfc860fdb04785d8185cd3c", "The major problem is with sendmail. -Mark Horton"], |
||||
["8a0a7c417a47deec0b6474d8c247da142d2e315113a2817af3de8f45690d8652", "Give me a rock, paper and scissors and I will move the world. CCFestoon"], |
||||
["310d263fdab056a930324cdea5f46f9ea70219c1a74b01009994484113222a62", "If the enemy is within range, then so are you."], |
||||
["1aaa0903aa4cf872fe494c322a6e535698ea2140e15f26fb6088287aedceb6ba", "It's well we cannot hear the screams/That we create in others' dreams."], |
||||
["2eb81bcaa9e9185a7587a1b26299dcfb30f2a58a7f29adb584b969725457ad4f", "You remind me of a TV show, but that's all right: I watch it anyway."], |
||||
["c27b1683ef76e274680ab5492e592997b0d9d5ac5a5f4651b6036f64215256af", "C is as portable as Stonehedge!!"], |
||||
["3995cce8f32b174c22ffac916124bd095c80205d9d5f1bb08a155ac24b40d6cb", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"], |
||||
["496f7063f8bd479bf54e9d87e9ba53e277839ac7fdaecc5105f2879b58ee562f", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"], |
||||
["2e0eff918940b01eea9539a02212f33ee84f77fab201f4287aa6167e4a1ed043", "How can you write a big system without C++? -Paul Glick"]] |
||||
|
||||
for vectorSet in testVectors: |
||||
assert vectorSet[0] == blake_hash(vectorSet[1]).encode('hex') |
@ -0,0 +1,3 @@ |
||||
from .mnemonic import Mnemonic |
||||
|
||||
__all__ = ["Mnemonic"] |
@ -0,0 +1,298 @@ |
||||
# |
||||
# Copyright (c) 2013 Pavol Rusnak |
||||
# Copyright (c) 2017 mruddy |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||
# this software and associated documentation files (the "Software"), to deal in |
||||
# the Software without restriction, including without limitation the rights to |
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
||||
# of the Software, and to permit persons to whom the Software is furnished to do |
||||
# so, subject to the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be included in all |
||||
# copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
# |
||||
from __future__ import annotations |
||||
|
||||
import hashlib |
||||
import hmac |
||||
import itertools |
||||
import os |
||||
import secrets |
||||
import typing as t |
||||
import unicodedata |
||||
|
||||
PBKDF2_ROUNDS = 2048 |
||||
|
||||
|
||||
class ConfigurationError(Exception): |
||||
pass |
||||
|
||||
|
||||
# Refactored code segments from <https://github.com/keis/base58> |
||||
def b58encode(v: bytes) -> str: |
||||
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" |
||||
|
||||
p, acc = 1, 0 |
||||
for c in reversed(v): |
||||
acc += p * c |
||||
p = p << 8 |
||||
|
||||
string = "" |
||||
while acc: |
||||
acc, idx = divmod(acc, 58) |
||||
string = alphabet[idx : idx + 1] + string |
||||
return string |
||||
|
||||
|
||||
class Mnemonic(object): |
||||
def __init__(self, language: str = "english", wordlist: list[str] | None = None): |
||||
self.radix = 2048 |
||||
self.language = language |
||||
|
||||
if wordlist is None: |
||||
d = os.path.join(os.path.dirname(__file__), f"wordlist/{language}.txt") |
||||
if os.path.exists(d) and os.path.isfile(d): |
||||
with open(d, "r", encoding="utf-8") as f: |
||||
wordlist = [w.strip() for w in f.readlines()] |
||||
else: |
||||
raise ConfigurationError("Language not detected") |
||||
|
||||
if len(wordlist) != self.radix: |
||||
raise ConfigurationError(f"Wordlist must contain {self.radix} words.") |
||||
|
||||
self.wordlist = wordlist |
||||
# Japanese must be joined by ideographic space |
||||
self.delimiter = "\u3000" if language == "japanese" else " " |
||||
|
||||
@classmethod |
||||
def list_languages(cls) -> list[str]: |
||||
return [ |
||||
f.split(".")[0] |
||||
for f in os.listdir(os.path.join(os.path.dirname(__file__), "wordlist")) |
||||
if f.endswith(".txt") |
||||
] |
||||
|
||||
@staticmethod |
||||
def normalize_string(txt: t.AnyStr) -> str: |
||||
if isinstance(txt, bytes): |
||||
utxt = txt.decode("utf8") |
||||
elif isinstance(txt, str): |
||||
utxt = txt |
||||
else: |
||||
raise TypeError("String value expected") |
||||
|
||||
return unicodedata.normalize("NFKD", utxt) |
||||
|
||||
@classmethod |
||||
def detect_language(cls, code: str) -> str: |
||||
"""Scan the Mnemonic until the language becomes unambiguous, including as abbreviation prefixes. |
||||
|
||||
Unfortunately, there are valid words that are ambiguous between languages, which are complete words |
||||
in one language and are prefixes in another: |
||||
|
||||
english: abandon ... about |
||||
french: abandon ... aboutir |
||||
|
||||
If prefixes remain ambiguous, require exactly one language where word(s) match exactly. |
||||
""" |
||||
code = cls.normalize_string(code) |
||||
possible = set(cls(lang) for lang in cls.list_languages()) |
||||
words = set(code.split()) |
||||
for word in words: |
||||
# possible languages have candidate(s) starting with the word/prefix |
||||
possible = set( |
||||
p for p in possible if any(c.startswith(word) for c in p.wordlist) |
||||
) |
||||
if not possible: |
||||
raise ConfigurationError(f"Language unrecognized for {word!r}") |
||||
if len(possible) == 1: |
||||
return possible.pop().language |
||||
# Multiple languages match: A prefix in many, but an exact match in one determines language. |
||||
complete = set() |
||||
for word in words: |
||||
exact = set(p for p in possible if word in p.wordlist) |
||||
if len(exact) == 1: |
||||
complete.update(exact) |
||||
if len(complete) == 1: |
||||
return complete.pop().language |
||||
raise ConfigurationError( |
||||
f"Language ambiguous between {', '.join(p.language for p in possible)}" |
||||
) |
||||
|
||||
def generate(self, strength: int = 128) -> str: |
||||
""" |
||||
Create a new mnemonic using a random generated number as entropy. |
||||
|
||||
As defined in BIP39, the entropy must be a multiple of 32 bits, and its size must be between 128 and 256 bits. |
||||
Therefore the possible values for `strength` are 128, 160, 192, 224 and 256. |
||||
|
||||
If not provided, the default entropy length will be set to 128 bits. |
||||
|
||||
The return is a list of words that encodes the generated entropy. |
||||
|
||||
:param strength: Number of bytes used as entropy |
||||
:type strength: int |
||||
:return: A randomly generated mnemonic |
||||
:rtype: str |
||||
""" |
||||
if strength not in [128, 160, 192, 224, 256]: |
||||
raise ValueError( |
||||
"Invalid strength value. Allowed values are [128, 160, 192, 224, 256]." |
||||
) |
||||
return self.to_mnemonic(secrets.token_bytes(strength // 8)) |
||||
|
||||
# Adapted from <http://tinyurl.com/oxmn476> |
||||
def to_entropy(self, words: list[str] | str) -> bytearray: |
||||
if not isinstance(words, list): |
||||
words = words.split(" ") |
||||
if len(words) not in [12, 15, 18, 21, 24]: |
||||
raise ValueError( |
||||
"Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)." |
||||
% len(words) |
||||
) |
||||
# Look up all the words in the list and construct the |
||||
# concatenation of the original entropy and the checksum. |
||||
concatLenBits = len(words) * 11 |
||||
concatBits = [False] * concatLenBits |
||||
wordindex = 0 |
||||
for word in words: |
||||
# Find the words index in the wordlist |
||||
ndx = self.wordlist.index(self.normalize_string(word)) |
||||
if ndx < 0: |
||||
raise LookupError('Unable to find "%s" in word list.' % word) |
||||
# Set the next 11 bits to the value of the index. |
||||
for ii in range(11): |
||||
concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0 |
||||
wordindex += 1 |
||||
checksumLengthBits = concatLenBits // 33 |
||||
entropyLengthBits = concatLenBits - checksumLengthBits |
||||
# Extract original entropy as bytes. |
||||
entropy = bytearray(entropyLengthBits // 8) |
||||
for ii in range(len(entropy)): |
||||
for jj in range(8): |
||||
if concatBits[(ii * 8) + jj]: |
||||
entropy[ii] |= 1 << (7 - jj) |
||||
# Take the digest of the entropy. |
||||
hashBytes = hashlib.sha256(entropy).digest() |
||||
hashBits = list( |
||||
itertools.chain.from_iterable( |
||||
[c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes |
||||
) |
||||
) |
||||
# Check all the checksum bits. |
||||
for i in range(checksumLengthBits): |
||||
if concatBits[entropyLengthBits + i] != hashBits[i]: |
||||
raise ValueError("Failed checksum.") |
||||
return entropy |
||||
|
||||
def to_mnemonic(self, data: bytes) -> str: |
||||
if len(data) not in [16, 20, 24, 28, 32]: |
||||
raise ValueError( |
||||
f"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not {len(data)}." |
||||
) |
||||
h = hashlib.sha256(data).hexdigest() |
||||
b = ( |
||||
bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8) |
||||
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32] |
||||
) |
||||
result = [] |
||||
for i in range(len(b) // 11): |
||||
idx = int(b[i * 11 : (i + 1) * 11], 2) |
||||
result.append(self.wordlist[idx]) |
||||
return self.delimiter.join(result) |
||||
|
||||
def check(self, mnemonic: str) -> bool: |
||||
mnemonic_list = self.normalize_string(mnemonic).split(" ") |
||||
# list of valid mnemonic lengths |
||||
if len(mnemonic_list) not in [12, 15, 18, 21, 24]: |
||||
return False |
||||
try: |
||||
idx = map( |
||||
lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic_list |
||||
) |
||||
b = "".join(idx) |
||||
except ValueError: |
||||
return False |
||||
l = len(b) # noqa: E741 |
||||
d = b[: l // 33 * 32] |
||||
h = b[-l // 33 :] |
||||
nd = int(d, 2).to_bytes(l // 33 * 4, byteorder="big") |
||||
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33] |
||||
return h == nh |
||||
|
||||
def expand_word(self, prefix: str) -> str: |
||||
if prefix in self.wordlist: |
||||
return prefix |
||||
else: |
||||
matches = [word for word in self.wordlist if word.startswith(prefix)] |
||||
if len(matches) == 1: # matched exactly one word in the wordlist |
||||
return matches[0] |
||||
else: |
||||
# exact match not found. |
||||
# this is not a validation routine, just return the input |
||||
return prefix |
||||
|
||||
def expand(self, mnemonic: str) -> str: |
||||
return " ".join(map(self.expand_word, mnemonic.split(" "))) |
||||
|
||||
@classmethod |
||||
def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes: |
||||
mnemonic = cls.normalize_string(mnemonic) |
||||
passphrase = cls.normalize_string(passphrase) |
||||
passphrase = "mnemonic" + passphrase |
||||
mnemonic_bytes = mnemonic.encode("utf-8") |
||||
passphrase_bytes = passphrase.encode("utf-8") |
||||
stretched = hashlib.pbkdf2_hmac( |
||||
"sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS |
||||
) |
||||
return stretched[:64] |
||||
|
||||
@staticmethod |
||||
def to_hd_master_key(seed: bytes, testnet: bool = False) -> str: |
||||
if len(seed) != 64: |
||||
raise ValueError("Provided seed should have length of 64") |
||||
|
||||
# Compute HMAC-SHA512 of seed |
||||
seed = hmac.new(b"Bitcoin seed", seed, digestmod=hashlib.sha512).digest() |
||||
|
||||
# Serialization format can be found at: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format |
||||
xprv = b"\x04\x88\xad\xe4" # Version for private mainnet |
||||
if testnet: |
||||
xprv = b"\x04\x35\x83\x94" # Version for private testnet |
||||
xprv += b"\x00" * 9 # Depth, parent fingerprint, and child number |
||||
xprv += seed[32:] # Chain code |
||||
xprv += b"\x00" + seed[:32] # Master key |
||||
|
||||
# Double hash using SHA256 |
||||
hashed_xprv = hashlib.sha256(xprv).digest() |
||||
hashed_xprv = hashlib.sha256(hashed_xprv).digest() |
||||
|
||||
# Append 4 bytes of checksum |
||||
xprv += hashed_xprv[:4] |
||||
|
||||
# Return base58 |
||||
return b58encode(xprv) |
||||
|
||||
|
||||
def main() -> None: |
||||
import sys |
||||
|
||||
if len(sys.argv) > 1: |
||||
hex_data = sys.argv[1] |
||||
else: |
||||
hex_data = sys.stdin.readline().strip() |
||||
data = bytes.fromhex(hex_data) |
||||
m = Mnemonic("english") |
||||
print(m.to_mnemonic(data)) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1 @@ |
||||
# Marker file for PEP 561. |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,13 +0,0 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2023 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
from enum import IntEnum |
||||
|
||||
|
||||
class Curves(IntEnum): |
||||
secp256k1 = 1 |
||||
ed25519 = 2 |
@ -0,0 +1,238 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
import threading |
||||
|
||||
from enum import IntEnum |
||||
|
||||
from basicswap.chainparams import ( |
||||
chainparams, |
||||
) |
||||
from basicswap.util import ( |
||||
ensure, |
||||
i2b, b2i, |
||||
make_int, |
||||
format_amount, |
||||
TemporaryError, |
||||
) |
||||
from basicswap.util.crypto import ( |
||||
hash160, |
||||
) |
||||
from basicswap.util.ecc import ( |
||||
ep, |
||||
getSecretInt, |
||||
) |
||||
from coincurve.dleag import ( |
||||
verify_secp256k1_point |
||||
) |
||||
from coincurve.keys import ( |
||||
PublicKey, |
||||
) |
||||
|
||||
|
||||
class Curves(IntEnum): |
||||
secp256k1 = 1 |
||||
ed25519 = 2 |
||||
|
||||
|
||||
class CoinInterface: |
||||
@staticmethod |
||||
def watch_blocks_for_scripts() -> bool: |
||||
return False |
||||
|
||||
@staticmethod |
||||
def compareFeeRates(a, b) -> bool: |
||||
return abs(a - b) < 20 |
||||
|
||||
def __init__(self, network): |
||||
self.setDefaults() |
||||
self._network = network |
||||
self._mx_wallet = threading.Lock() |
||||
|
||||
def setDefaults(self): |
||||
self._unknown_wallet_seed = True |
||||
self._restore_height = None |
||||
|
||||
def make_int(self, amount_in: int, r: int = 0) -> int: |
||||
return make_int(amount_in, self.exp(), r=r) |
||||
|
||||
def format_amount(self, amount_in, conv_int=False, r=0): |
||||
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in |
||||
return format_amount(amount_int, self.exp()) |
||||
|
||||
def coin_name(self) -> str: |
||||
coin_chainparams = chainparams[self.coin_type()] |
||||
if coin_chainparams.get('use_ticker_as_name', False): |
||||
return coin_chainparams['ticker'] |
||||
return coin_chainparams['name'].capitalize() |
||||
|
||||
def ticker(self) -> str: |
||||
ticker = chainparams[self.coin_type()]['ticker'] |
||||
if self._network == 'testnet': |
||||
ticker = 't' + ticker |
||||
elif self._network == 'regtest': |
||||
ticker = 'rt' + ticker |
||||
return ticker |
||||
|
||||
def getExchangeTicker(self, exchange_name: str) -> str: |
||||
return chainparams[self.coin_type()]['ticker'] |
||||
|
||||
def getExchangeName(self, exchange_name: str) -> str: |
||||
return chainparams[self.coin_type()]['name'] |
||||
|
||||
def ticker_mainnet(self) -> str: |
||||
ticker = chainparams[self.coin_type()]['ticker'] |
||||
return ticker |
||||
|
||||
def min_amount(self) -> int: |
||||
return chainparams[self.coin_type()][self._network]['min_amount'] |
||||
|
||||
def max_amount(self) -> int: |
||||
return chainparams[self.coin_type()][self._network]['max_amount'] |
||||
|
||||
def setWalletSeedWarning(self, value: bool) -> None: |
||||
self._unknown_wallet_seed = value |
||||
|
||||
def setWalletRestoreHeight(self, value: int) -> None: |
||||
self._restore_height = value |
||||
|
||||
def knownWalletSeed(self) -> bool: |
||||
return not self._unknown_wallet_seed |
||||
|
||||
def chainparams(self): |
||||
return chainparams[self.coin_type()] |
||||
|
||||
def chainparams_network(self): |
||||
return chainparams[self.coin_type()][self._network] |
||||
|
||||
def has_segwit(self) -> bool: |
||||
return chainparams[self.coin_type()].get('has_segwit', True) |
||||
|
||||
def use_p2shp2wsh(self) -> bool: |
||||
# p2sh-p2wsh |
||||
return False |
||||
|
||||
def is_transient_error(self, ex) -> bool: |
||||
if isinstance(ex, TemporaryError): |
||||
return True |
||||
str_error: str = str(ex).lower() |
||||
if 'not enough unlocked money' in str_error: |
||||
return True |
||||
if 'no unlocked balance' in str_error: |
||||
return True |
||||
if 'transaction was rejected by daemon' in str_error: |
||||
return True |
||||
if 'invalid unlocked_balance' in str_error: |
||||
return True |
||||
if 'daemon is busy' in str_error: |
||||
return True |
||||
if 'timed out' in str_error: |
||||
return True |
||||
if 'request-sent' in str_error: |
||||
return True |
||||
return False |
||||
|
||||
def setConfTarget(self, new_conf_target: int) -> None: |
||||
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value') |
||||
self._conf_target = new_conf_target |
||||
|
||||
def walletRestoreHeight(self) -> int: |
||||
return self._restore_height |
||||
|
||||
def get_connection_type(self): |
||||
return self._connection_type |
||||
|
||||
def using_segwit(self) -> bool: |
||||
# Using btc native segwit |
||||
return self._use_segwit |
||||
|
||||
def use_tx_vsize(self) -> bool: |
||||
return self._use_segwit |
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap) -> int: |
||||
return bid.amount |
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap) -> int: |
||||
return xmr_swap.a_swap_refund_value |
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap) -> int: |
||||
# Only one prevout exists |
||||
return 0 |
||||
|
||||
def checkWallets(self) -> int: |
||||
return 1 |
||||
|
||||
|
||||
class AdaptorSigInterface(): |
||||
def getScriptLockTxDummyWitness(self, script: bytes): |
||||
return [ |
||||
b'', |
||||
bytes(72), |
||||
bytes(72), |
||||
bytes(len(script)) |
||||
] |
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes): |
||||
return [ |
||||
b'', |
||||
bytes(72), |
||||
bytes(72), |
||||
bytes((1,)), |
||||
bytes(len(script)) |
||||
] |
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes): |
||||
return [ |
||||
bytes(72), |
||||
b'', |
||||
bytes(len(script)) |
||||
] |
||||
|
||||
|
||||
class Secp256k1Interface(CoinInterface, AdaptorSigInterface): |
||||
@staticmethod |
||||
def curve_type(): |
||||
return Curves.secp256k1 |
||||
|
||||
def getNewSecretKey(self) -> bytes: |
||||
return i2b(getSecretInt()) |
||||
|
||||
def getPubkey(self, privkey: bytes) -> bytes: |
||||
return PublicKey.from_secret(privkey).format() |
||||
|
||||
def pkh(self, pubkey: bytes) -> bytes: |
||||
return hash160(pubkey) |
||||
|
||||
def verifyKey(self, k: bytes) -> bool: |
||||
i = b2i(k) |
||||
return (i < ep.o and i > 0) |
||||
|
||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool: |
||||
return verify_secp256k1_point(pubkey_bytes) |
||||
|
||||
def isValidAddressHash(self, address_hash: bytes) -> bool: |
||||
hash_len = len(address_hash) |
||||
if hash_len == 20: |
||||
return True |
||||
|
||||
def isValidPubkey(self, pubkey: bytes) -> bool: |
||||
try: |
||||
self.verifyPubkey(pubkey) |
||||
return True |
||||
except Exception: |
||||
return False |
||||
|
||||
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool: |
||||
pubkey = PublicKey(pubkey) |
||||
return pubkey.verify(sig, signed_hash, hasher=None) |
||||
|
||||
def sumKeys(self, ka: bytes, kb: bytes) -> bytes: |
||||
# TODO: Add to coincurve |
||||
return i2b((b2i(ka) + b2i(kb)) % ep.o) |
||||
|
||||
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes: |
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format() |
@ -0,0 +1,4 @@ |
||||
|
||||
from .dcr import DCRInterface |
||||
|
||||
__all__ = ['DCRInterface',] |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,204 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
import copy |
||||
from enum import IntEnum |
||||
from basicswap.util.crypto import blake256 |
||||
from basicswap.util.integer import decode_compactsize, encode_compactsize |
||||
|
||||
|
||||
class TxSerializeType(IntEnum): |
||||
Full = 0 |
||||
NoWitness = 1 |
||||
OnlyWitness = 2 |
||||
|
||||
|
||||
class SigHashType(IntEnum): |
||||
SigHashAll = 0x1 |
||||
SigHashNone = 0x2 |
||||
SigHashSingle = 0x3 |
||||
SigHashAnyOneCanPay = 0x80 |
||||
|
||||
SigHashMask = 0x1f |
||||
|
||||
|
||||
class SignatureType(IntEnum): |
||||
STEcdsaSecp256k1 = 0 |
||||
STEd25519 = 1 |
||||
STSchnorrSecp256k1 = 2 |
||||
|
||||
|
||||
class COutPoint: |
||||
__slots__ = ('hash', 'n', 'tree') |
||||
|
||||
def __init__(self, hash=0, n=0, tree=0): |
||||
self.hash = hash |
||||
self.n = n |
||||
self.tree = tree |
||||
|
||||
def get_hash(self) -> bytes: |
||||
return self.hash.to_bytes(32, 'big') |
||||
|
||||
|
||||
class CTxIn: |
||||
__slots__ = ('prevout', 'sequence', |
||||
'value_in', 'block_height', 'block_index', 'signature_script') # Witness |
||||
|
||||
def __init__(self, prevout=COutPoint(), sequence=0): |
||||
self.prevout = prevout |
||||
self.sequence = sequence |
||||
self.value_in = -1 |
||||
self.block_height = 0 |
||||
self.block_index = 0xffffffff |
||||
self.signature_script = bytes() |
||||
|
||||
|
||||
class CTxOut: |
||||
__slots__ = ('value', 'version', 'script_pubkey') |
||||
|
||||
def __init__(self, value=0, script_pubkey=bytes()): |
||||
self.value = value |
||||
self.version = 0 |
||||
self.script_pubkey = script_pubkey |
||||
|
||||
|
||||
class CTransaction: |
||||
__slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry') |
||||
|
||||
def __init__(self, tx=None): |
||||
if tx is None: |
||||
self.version = 1 |
||||
self.vin = [] |
||||
self.vout = [] |
||||
self.locktime = 0 |
||||
self.expiry = 0 |
||||
else: |
||||
self.version = tx.version |
||||
self.vin = copy.deepcopy(tx.vin) |
||||
self.vout = copy.deepcopy(tx.vout) |
||||
self.locktime = tx.locktime |
||||
self.expiry = tx.expiry |
||||
|
||||
def deserialize(self, data: bytes) -> None: |
||||
|
||||
version = int.from_bytes(data[:4], 'little') |
||||
self.version = version & 0xffff |
||||
ser_type: int = version >> 16 |
||||
o = 4 |
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness: |
||||
num_txin, nb = decode_compactsize(data, o) |
||||
o += nb |
||||
|
||||
for i in range(num_txin): |
||||
txi = CTxIn() |
||||
txi.prevout = COutPoint() |
||||
txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little') |
||||
o += 32 |
||||
txi.prevout.n = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
txi.prevout.tree = data[o] |
||||
o += 1 |
||||
txi.sequence = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
self.vin.append(txi) |
||||
|
||||
num_txout, nb = decode_compactsize(data, o) |
||||
o += nb |
||||
|
||||
for i in range(num_txout): |
||||
txo = CTxOut() |
||||
txo.value = int.from_bytes(data[o:o + 8], 'little') |
||||
o += 8 |
||||
txo.version = int.from_bytes(data[o:o + 2], 'little') |
||||
o += 2 |
||||
script_bytes, nb = decode_compactsize(data, o) |
||||
o += nb |
||||
txo.script_pubkey = data[o:o + script_bytes] |
||||
o += script_bytes |
||||
self.vout.append(txo) |
||||
|
||||
self.locktime = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
self.expiry = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
|
||||
if ser_type == TxSerializeType.NoWitness: |
||||
return |
||||
|
||||
num_wit_scripts, nb = decode_compactsize(data, o) |
||||
o += nb |
||||
|
||||
if ser_type == TxSerializeType.OnlyWitness: |
||||
self.vin = [CTxIn() for _ in range(num_wit_scripts)] |
||||
else: |
||||
if num_wit_scripts != len(self.vin): |
||||
raise ValueError('non equal witness and prefix txin quantities') |
||||
|
||||
for i in range(num_wit_scripts): |
||||
txi = self.vin[i] |
||||
txi.value_in = int.from_bytes(data[o:o + 8], 'little') |
||||
o += 8 |
||||
txi.block_height = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
txi.block_index = int.from_bytes(data[o:o + 4], 'little') |
||||
o += 4 |
||||
script_bytes, nb = decode_compactsize(data, o) |
||||
o += nb |
||||
txi.signature_script = data[o:o + script_bytes] |
||||
o += script_bytes |
||||
|
||||
def serialize(self, ser_type=TxSerializeType.Full) -> bytes: |
||||
data = bytes() |
||||
version = (self.version & 0xffff) | (ser_type << 16) |
||||
data += version.to_bytes(4, 'little') |
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness: |
||||
data += encode_compactsize(len(self.vin)) |
||||
for txi in self.vin: |
||||
data += txi.prevout.hash.to_bytes(32, 'little') |
||||
data += txi.prevout.n.to_bytes(4, 'little') |
||||
data += txi.prevout.tree.to_bytes(1, 'little') |
||||
data += txi.sequence.to_bytes(4, 'little') |
||||
|
||||
data += encode_compactsize(len(self.vout)) |
||||
for txo in self.vout: |
||||
data += txo.value.to_bytes(8, 'little') |
||||
data += txo.version.to_bytes(2, 'little') |
||||
data += encode_compactsize(len(txo.script_pubkey)) |
||||
data += txo.script_pubkey |
||||
|
||||
data += self.locktime.to_bytes(4, 'little') |
||||
data += self.expiry.to_bytes(4, 'little') |
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness: |
||||
data += encode_compactsize(len(self.vin)) |
||||
for txi in self.vin: |
||||
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values |
||||
data += tc_value_in.to_bytes(8, 'little') |
||||
data += txi.block_height.to_bytes(4, 'little') |
||||
data += txi.block_index.to_bytes(4, 'little') |
||||
data += encode_compactsize(len(txi.signature_script)) |
||||
data += txi.signature_script |
||||
|
||||
return data |
||||
|
||||
def TxHash(self) -> bytes: |
||||
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1] |
||||
|
||||
def TxHashWitness(self) -> bytes: |
||||
raise ValueError('todo') |
||||
|
||||
def TxHashFull(self) -> bytes: |
||||
raise ValueError('todo') |
||||
|
||||
|
||||
def findOutput(tx, script_pk: bytes): |
||||
for i in range(len(tx.vout)): |
||||
if tx.vout[i].script_pubkey == script_pk: |
||||
return i |
||||
return None |
@ -0,0 +1,47 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
import json |
||||
import traceback |
||||
from basicswap.rpc import Jsonrpc |
||||
|
||||
|
||||
def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'): |
||||
try: |
||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) |
||||
x = Jsonrpc(url) |
||||
x.__handler = None |
||||
v = x.json_request(method, params) |
||||
x.close() |
||||
r = json.loads(v.decode('utf-8')) |
||||
except Exception as ex: |
||||
traceback.print_exc() |
||||
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method) |
||||
|
||||
if 'error' in r and r['error'] is not None: |
||||
raise ValueError('RPC error ' + str(r['error'])) |
||||
|
||||
return r['result'] |
||||
|
||||
|
||||
def openrpc(rpc_port, auth, host='127.0.0.1'): |
||||
try: |
||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) |
||||
return Jsonrpc(url) |
||||
except Exception as ex: |
||||
traceback.print_exc() |
||||
raise ValueError('RPC error ' + str(ex)) |
||||
|
||||
|
||||
def make_rpc_func(port, auth, host='127.0.0.1'): |
||||
port = port |
||||
auth = auth |
||||
host = host |
||||
|
||||
def rpc_func(method, params=None): |
||||
nonlocal port, auth, host |
||||
return callrpc(port, auth, method, params, host) |
||||
return rpc_func |
@ -0,0 +1,50 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
|
||||
OP_0 = 0x00 |
||||
OP_DATA_1 = 0x01 |
||||
OP_1NEGATE = 0x4f |
||||
OP_1 = 0x51 |
||||
OP_IF = 0x63 |
||||
OP_ELSE = 0x67 |
||||
OP_ENDIF = 0x68 |
||||
OP_DROP = 0x75 |
||||
OP_DUP = 0x76 |
||||
OP_EQUAL = 0x87 |
||||
OP_EQUALVERIFY = 0x88 |
||||
OP_PUSHDATA1 = 0x4c |
||||
OP_PUSHDATA2 = 0x4d |
||||
OP_PUSHDATA4 = 0x4e |
||||
OP_HASH160 = 0xa9 |
||||
OP_CHECKSIG = 0xac |
||||
OP_CHECKMULTISIG = 0xae |
||||
OP_CHECKSEQUENCEVERIFY = 0xb2 |
||||
|
||||
|
||||
def push_script_data(data_array: bytearray, data: bytes) -> None: |
||||
len_data: int = len(data) |
||||
|
||||
if len_data == 0 or (len_data == 1 and data[0] == 0): |
||||
data_array += bytes((OP_0,)) |
||||
return |
||||
if len_data == 1 and data[0] <= 16: |
||||
data_array += bytes((OP_1 - 1 + data[0],)) |
||||
return |
||||
if len_data == 1 and data[0] == 0x81: |
||||
data_array += bytes((OP_1NEGATE,)) |
||||
return |
||||
|
||||
if len_data < OP_PUSHDATA1: |
||||
data_array += len_data.to_bytes(1, 'little') |
||||
elif len_data <= 0xff: |
||||
data_array += bytes((OP_PUSHDATA1, len_data)) |
||||
elif len_data <= 0xffff: |
||||
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little') |
||||
else: |
||||
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little') |
||||
|
||||
data_array += data |
@ -0,0 +1,66 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
import os |
||||
import select |
||||
import subprocess |
||||
|
||||
|
||||
def createDCRWallet(args, hex_seed, logging, delay_event): |
||||
logging.info('Creating DCR wallet') |
||||
|
||||
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read |
||||
|
||||
if os.name == 'nt': |
||||
str_args = ' '.join(args) |
||||
p = subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) |
||||
else: |
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) |
||||
|
||||
def readOutput(): |
||||
buf = os.read(pipe_r, 1024).decode('utf-8') |
||||
response = None |
||||
if 'Opened wallet' in buf: |
||||
pass |
||||
elif 'Use the existing configured private passphrase' in buf: |
||||
response = b'y\n' |
||||
elif 'Do you want to add an additional layer of encryption' in buf: |
||||
response = b'n\n' |
||||
elif 'Do you have an existing wallet seed' in buf: |
||||
response = b'y\n' |
||||
elif 'Enter existing wallet seed' in buf: |
||||
response = (hex_seed + '\n').encode('utf-8') |
||||
elif 'Seed input successful' in buf: |
||||
pass |
||||
elif 'Upgrading database from version' in buf: |
||||
pass |
||||
elif 'Ticket commitments db upgrade done' in buf: |
||||
pass |
||||
elif 'The wallet has been created successfully' in buf: |
||||
pass |
||||
else: |
||||
raise ValueError(f'Unexpected output: {buf}') |
||||
if response is not None: |
||||
p.stdin.write(response) |
||||
p.stdin.flush() |
||||
|
||||
try: |
||||
while p.poll() is None: |
||||
if os.name == 'nt': |
||||
readOutput() |
||||
delay_event.wait(0.1) |
||||
continue |
||||
while len(select.select([pipe_r], [], [], 0)[0]) == 1: |
||||
readOutput() |
||||
delay_event.wait(0.1) |
||||
except Exception as e: |
||||
logging.error(f'dcrwallet --create failed: {e}') |
||||
finally: |
||||
if p.poll() is None: |
||||
p.terminate() |
||||
os.close(pipe_r) |
||||
os.close(pipe_w) |
||||
p.stdin.close() |
@ -0,0 +1,27 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
from basicswap.chainparams import WOW_COIN, Coins |
||||
from .xmr import XMRInterface |
||||
|
||||
|
||||
class WOWInterface(XMRInterface): |
||||
|
||||
@staticmethod |
||||
def coin_type(): |
||||
return Coins.WOW |
||||
|
||||
@staticmethod |
||||
def ticker_str() -> int: |
||||
return Coins.WOW.name |
||||
|
||||
@staticmethod |
||||
def COIN(): |
||||
return WOW_COIN |
||||
|
||||
@staticmethod |
||||
def exp() -> int: |
||||
return 11 |
@ -1,156 +0,0 @@ |
||||
syntax = "proto3"; |
||||
|
||||
package basicswap; |
||||
|
||||
/* Step 1, seller -> network */ |
||||
message OfferMessage { |
||||
uint32 protocol_version = 1; |
||||
uint32 coin_from = 2; |
||||
uint32 coin_to = 3; |
||||
uint64 amount_from = 4; |
||||
uint64 amount_to = 5; |
||||
uint64 min_bid_amount = 6; |
||||
uint64 time_valid = 7; |
||||
enum LockType { |
||||
NOT_SET = 0; |
||||
SEQUENCE_LOCK_BLOCKS = 1; |
||||
SEQUENCE_LOCK_TIME = 2; |
||||
ABS_LOCK_BLOCKS = 3; |
||||
ABS_LOCK_TIME = 4; |
||||
} |
||||
LockType lock_type = 8; |
||||
uint32 lock_value = 9; |
||||
uint32 swap_type = 10; |
||||
|
||||
/* optional */ |
||||
string proof_address = 11; |
||||
string proof_signature = 12; |
||||
bytes pkhash_seller = 13; |
||||
bytes secret_hash = 14; |
||||
|
||||
uint64 fee_rate_from = 15; |
||||
uint64 fee_rate_to = 16; |
||||
|
||||
bool amount_negotiable = 17; |
||||
bool rate_negotiable = 18; |
||||
|
||||
bytes proof_utxos = 19; /* 32 byte txid 2 byte vout, repeated */ |
||||
} |
||||
|
||||
/* Step 2, buyer -> seller */ |
||||
message BidMessage { |
||||
uint32 protocol_version = 1; |
||||
bytes offer_msg_id = 2; |
||||
uint64 time_valid = 3; /* seconds bid is valid for */ |
||||
uint64 amount = 4; /* amount of amount_from bid is for */ |
||||
uint64 amount_to = 5; |
||||
bytes pkhash_buyer = 6; /* buyer's address to receive amount_from */ |
||||
string proof_address = 7; |
||||
string proof_signature = 8; |
||||
|
||||
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */ |
||||
} |
||||
|
||||
/* For tests */ |
||||
message BidMessage_test { |
||||
uint32 protocol_version = 1; |
||||
bytes offer_msg_id = 2; |
||||
uint64 time_valid = 3; |
||||
uint64 amount = 4; |
||||
uint64 rate = 5; |
||||
} |
||||
|
||||
/* Step 3, seller -> buyer */ |
||||
message BidAcceptMessage { |
||||
bytes bid_msg_id = 1; |
||||
bytes initiate_txid = 2; |
||||
bytes contract_script = 3; |
||||
} |
||||
|
||||
message OfferRevokeMessage { |
||||
bytes offer_msg_id = 1; |
||||
bytes signature = 2; |
||||
} |
||||
|
||||
message BidRejectMessage { |
||||
bytes bid_msg_id = 1; |
||||
uint32 reject_code = 2; |
||||
} |
||||
|
||||
message XmrBidMessage { |
||||
/* MSG1L, F -> L */ |
||||
uint32 protocol_version = 1; |
||||
bytes offer_msg_id = 2; |
||||
uint64 time_valid = 3; /* seconds bid is valid for */ |
||||
uint64 amount = 4; /* amount of amount_from bid is for */ |
||||
uint64 amount_to = 5; |
||||
|
||||
bytes pkaf = 6; |
||||
|
||||
bytes kbvf = 7; |
||||
bytes kbsf_dleag = 8; |
||||
|
||||
bytes dest_af = 9; |
||||
} |
||||
|
||||
message XmrSplitMessage { |
||||
bytes msg_id = 1; |
||||
uint32 msg_type = 2; /* 1 XmrBid, 2 XmrBidAccept */ |
||||
uint32 sequence = 3; |
||||
bytes dleag = 4; |
||||
} |
||||
|
||||
message XmrBidAcceptMessage { |
||||
bytes bid_msg_id = 1; |
||||
|
||||
bytes pkal = 2; |
||||
bytes kbvl = 3; |
||||
bytes kbsl_dleag = 4; |
||||
|
||||
/* MSG2F */ |
||||
bytes a_lock_tx = 5; |
||||
bytes a_lock_tx_script = 6; |
||||
bytes a_lock_refund_tx = 7; |
||||
bytes a_lock_refund_tx_script = 8; |
||||
bytes a_lock_refund_spend_tx = 9; |
||||
bytes al_lock_refund_tx_sig = 10; |
||||
} |
||||
|
||||
message XmrBidLockTxSigsMessage { |
||||
/* MSG3L */ |
||||
bytes bid_msg_id = 1; |
||||
bytes af_lock_refund_spend_tx_esig = 2; |
||||
bytes af_lock_refund_tx_sig = 3; |
||||
} |
||||
|
||||
message XmrBidLockSpendTxMessage { |
||||
/* MSG4F */ |
||||
bytes bid_msg_id = 1; |
||||
bytes a_lock_spend_tx = 2; |
||||
bytes kal_sig = 3; |
||||
} |
||||
|
||||
message XmrBidLockReleaseMessage { |
||||
/* MSG5F */ |
||||
bytes bid_msg_id = 1; |
||||
bytes al_lock_spend_tx_esig = 2; |
||||
} |
||||
|
||||
message ADSBidIntentMessage { |
||||
/* L -> F Sent from bidder, construct a reverse bid */ |
||||
uint32 protocol_version = 1; |
||||
bytes offer_msg_id = 2; |
||||
uint64 time_valid = 3; /* seconds bid is valid for */ |
||||
uint64 amount_from = 4; /* amount of offer.coin_from bid is for */ |
||||
uint64 amount_to = 5; /* amount of offer.coin_to bid is for, equivalent to bid.amount */ |
||||
} |
||||
|
||||
message ADSBidIntentAcceptMessage { |
||||
/* F -> L Sent from offerer, construct a reverse bid */ |
||||
bytes bid_msg_id = 1; |
||||
|
||||
bytes pkaf = 2; |
||||
bytes kbvf = 3; |
||||
bytes kbsf_dleag = 4; |
||||
bytes dest_af = 5; |
||||
} |
@ -0,0 +1,265 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
|
||||
''' |
||||
syntax = "proto3"; |
||||
|
||||
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum |
||||
1 I64 fixed64, sfixed64, double |
||||
2 LEN string, bytes, embedded messages, packed repeated fields |
||||
5 I32 fixed32, sfixed32, float |
||||
|
||||
Don't encode fields of default values. |
||||
When decoding initialise all fields not set from data. |
||||
|
||||
protobuf ParseFromString would reset the whole object, from_bytes won't. |
||||
''' |
||||
|
||||
from basicswap.util.integer import encode_varint, decode_varint |
||||
|
||||
|
||||
class NonProtobufClass(): |
||||
def __init__(self, init_all: bool = True, **kwargs): |
||||
for key, value in kwargs.items(): |
||||
found_field: bool = False |
||||
for field_num, v in self._map.items(): |
||||
field_name, wire_type, field_type = v |
||||
if field_name == key: |
||||
setattr(self, field_name, value) |
||||
found_field = True |
||||
break |
||||
if found_field is False: |
||||
raise ValueError(f'got an unexpected keyword argument \'{key}\'') |
||||
|
||||
if init_all: |
||||
self.init_fields() |
||||
|
||||
def init_fields(self) -> None: |
||||
# Set default values for missing fields |
||||
for field_num, v in self._map.items(): |
||||
field_name, wire_type, field_type = v |
||||
if hasattr(self, field_name): |
||||
continue |
||||
if wire_type == 0: |
||||
setattr(self, field_name, 0) |
||||
elif wire_type == 2: |
||||
if field_type == 1: |
||||
setattr(self, field_name, str()) |
||||
else: |
||||
setattr(self, field_name, bytes()) |
||||
else: |
||||
raise ValueError(f'Unknown wire_type {wire_type}') |
||||
|
||||
def to_bytes(self) -> bytes: |
||||
rv = bytes() |
||||
|
||||
for field_num, v in self._map.items(): |
||||
field_name, wire_type, field_type = v |
||||
if not hasattr(self, field_name): |
||||
continue |
||||
field_value = getattr(self, field_name) |
||||
tag = (field_num << 3) | wire_type |
||||
if wire_type == 0: |
||||
if field_value == 0: |
||||
continue |
||||
rv += encode_varint(tag) |
||||
rv += encode_varint(field_value) |
||||
elif wire_type == 2: |
||||
if len(field_value) == 0: |
||||
continue |
||||
rv += encode_varint(tag) |
||||
if isinstance(field_value, str): |
||||
field_value = field_value.encode('utf-8') |
||||
rv += encode_varint(len(field_value)) |
||||
rv += field_value |
||||
else: |
||||
raise ValueError(f'Unknown wire_type {wire_type}') |
||||
return rv |
||||
|
||||
def from_bytes(self, b: bytes, init_all: bool = True) -> None: |
||||
max_len: int = len(b) |
||||
o: int = 0 |
||||
while o < max_len: |
||||
tag, lv = decode_varint(b, o) |
||||
o += lv |
||||
wire_type = tag & 7 |
||||
field_num = tag >> 3 |
||||
|
||||
field_name, wire_type_expect, field_type = self._map[field_num] |
||||
if wire_type != wire_type_expect: |
||||
raise ValueError(f'Unexpected wire_type {wire_type} for field {field_num}') |
||||
|
||||
if wire_type == 0: |
||||
field_value, lv = decode_varint(b, o) |
||||
o += lv |
||||
elif wire_type == 2: |
||||
field_len, lv = decode_varint(b, o) |
||||
o += lv |
||||
field_value = b[o: o + field_len] |
||||
o += field_len |
||||
if field_type == 1: |
||||
field_value = field_value.decode('utf-8') |
||||
else: |
||||
raise ValueError(f'Unknown wire_type {wire_type}') |
||||
|
||||
setattr(self, field_name, field_value) |
||||
|
||||
if init_all: |
||||
self.init_fields() |
||||
|
||||
|
||||
class OfferMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('protocol_version', 0, 0), |
||||
2: ('coin_from', 0, 0), |
||||
3: ('coin_to', 0, 0), |
||||
4: ('amount_from', 0, 0), |
||||
5: ('amount_to', 0, 0), |
||||
6: ('min_bid_amount', 0, 0), |
||||
7: ('time_valid', 0, 0), |
||||
8: ('lock_type', 0, 0), |
||||
9: ('lock_value', 0, 0), |
||||
10: ('swap_type', 0, 0), |
||||
11: ('proof_address', 2, 1), |
||||
12: ('proof_signature', 2, 1), |
||||
13: ('pkhash_seller', 2, 0), |
||||
14: ('secret_hash', 2, 0), |
||||
15: ('fee_rate_from', 0, 0), |
||||
16: ('fee_rate_to', 0, 0), |
||||
17: ('amount_negotiable', 0, 2), |
||||
18: ('rate_negotiable', 0, 2), |
||||
19: ('proof_utxos', 2, 0), |
||||
} |
||||
|
||||
|
||||
class BidMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('protocol_version', 0, 0), |
||||
2: ('offer_msg_id', 2, 0), |
||||
3: ('time_valid', 0, 0), |
||||
4: ('amount', 0, 0), |
||||
5: ('amount_to', 0, 0), |
||||
6: ('pkhash_buyer', 2, 0), |
||||
7: ('proof_address', 2, 1), |
||||
8: ('proof_signature', 2, 1), |
||||
9: ('proof_utxos', 2, 0), |
||||
10: ('pkhash_buyer_to', 2, 0), |
||||
} |
||||
|
||||
|
||||
class BidAcceptMessage(NonProtobufClass): |
||||
# Step 3, seller -> buyer |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('initiate_txid', 2, 0), |
||||
3: ('contract_script', 2, 0), |
||||
4: ('pkhash_seller', 2, 0), |
||||
} |
||||
|
||||
|
||||
class OfferRevokeMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('offer_msg_id', 2, 0), |
||||
2: ('signature', 2, 0), |
||||
} |
||||
|
||||
|
||||
class BidRejectMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('reject_code', 0, 0), |
||||
} |
||||
|
||||
|
||||
class XmrBidMessage(NonProtobufClass): |
||||
# MSG1L, F -> L |
||||
_map = { |
||||
1: ('protocol_version', 0, 0), |
||||
2: ('offer_msg_id', 2, 0), |
||||
3: ('time_valid', 0, 0), |
||||
4: ('amount', 0, 0), |
||||
5: ('amount_to', 0, 0), |
||||
6: ('pkaf', 2, 0), |
||||
7: ('kbvf', 2, 0), |
||||
8: ('kbsf_dleag', 2, 0), |
||||
9: ('dest_af', 2, 0), |
||||
} |
||||
|
||||
|
||||
class XmrSplitMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('msg_id', 2, 0), |
||||
2: ('msg_type', 0, 0), |
||||
3: ('sequence', 0, 0), |
||||
4: ('dleag', 2, 0), |
||||
} |
||||
|
||||
|
||||
class XmrBidAcceptMessage(NonProtobufClass): |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('pkal', 2, 0), |
||||
3: ('kbvl', 2, 0), |
||||
4: ('kbsl_dleag', 2, 0), |
||||
|
||||
# MSG2F |
||||
5: ('a_lock_tx', 2, 0), |
||||
6: ('a_lock_tx_script', 2, 0), |
||||
7: ('a_lock_refund_tx', 2, 0), |
||||
8: ('a_lock_refund_tx_script', 2, 0), |
||||
9: ('a_lock_refund_spend_tx', 2, 0), |
||||
10: ('al_lock_refund_tx_sig', 2, 0), |
||||
} |
||||
|
||||
|
||||
class XmrBidLockTxSigsMessage(NonProtobufClass): |
||||
# MSG3L |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('af_lock_refund_spend_tx_esig', 2, 0), |
||||
3: ('af_lock_refund_tx_sig', 2, 0), |
||||
} |
||||
|
||||
|
||||
class XmrBidLockSpendTxMessage(NonProtobufClass): |
||||
# MSG4F |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('a_lock_spend_tx', 2, 0), |
||||
3: ('kal_sig', 2, 0), |
||||
} |
||||
|
||||
|
||||
class XmrBidLockReleaseMessage(NonProtobufClass): |
||||
# MSG5F |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('al_lock_spend_tx_esig', 2, 0), |
||||
} |
||||
|
||||
|
||||
class ADSBidIntentMessage(NonProtobufClass): |
||||
# L -> F Sent from bidder, construct a reverse bid |
||||
_map = { |
||||
1: ('protocol_version', 0, 0), |
||||
2: ('offer_msg_id', 2, 0), |
||||
3: ('time_valid', 0, 0), |
||||
4: ('amount_from', 0, 0), |
||||
5: ('amount_to', 0, 0), |
||||
} |
||||
|
||||
|
||||
class ADSBidIntentAcceptMessage(NonProtobufClass): |
||||
# F -> L Sent from offerer, construct a reverse bid |
||||
_map = { |
||||
1: ('bid_msg_id', 2, 0), |
||||
2: ('pkaf', 2, 0), |
||||
3: ('kbvf', 2, 0), |
||||
4: ('kbsf_dleag', 2, 0), |
||||
5: ('dest_af', 2, 0), |
||||
} |
@ -1,54 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Generated by the protocol buffer compiler. DO NOT EDIT! |
||||
# source: messages.proto |
||||
# Protobuf Python Version: 4.25.3 |
||||
"""Generated protocol buffer code.""" |
||||
from google.protobuf import descriptor as _descriptor |
||||
from google.protobuf import descriptor_pool as _descriptor_pool |
||||
from google.protobuf import symbol_database as _symbol_database |
||||
from google.protobuf.internal import builder as _builder |
||||
# @@protoc_insertion_point(imports) |
||||
|
||||
_sym_db = _symbol_database.Default() |
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xce\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3') |
||||
|
||||
_globals = globals() |
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) |
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', _globals) |
||||
if _descriptor._USE_C_DESCRIPTORS == False: |
||||
DESCRIPTOR._options = None |
||||
_globals['_OFFERMESSAGE']._serialized_start=30 |
||||
_globals['_OFFERMESSAGE']._serialized_end=606 |
||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=493 |
||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=606 |
||||
_globals['_BIDMESSAGE']._serialized_start=609 |
||||
_globals['_BIDMESSAGE']._serialized_end=815 |
||||
_globals['_BIDMESSAGE_TEST']._serialized_start=817 |
||||
_globals['_BIDMESSAGE_TEST']._serialized_end=932 |
||||
_globals['_BIDACCEPTMESSAGE']._serialized_start=934 |
||||
_globals['_BIDACCEPTMESSAGE']._serialized_end=1020 |
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1022 |
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1083 |
||||
_globals['_BIDREJECTMESSAGE']._serialized_start=1085 |
||||
_globals['_BIDREJECTMESSAGE']._serialized_end=1144 |
||||
_globals['_XMRBIDMESSAGE']._serialized_start=1147 |
||||
_globals['_XMRBIDMESSAGE']._serialized_end=1330 |
||||
_globals['_XMRSPLITMESSAGE']._serialized_start=1332 |
||||
_globals['_XMRSPLITMESSAGE']._serialized_end=1416 |
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1419 |
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1675 |
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1677 |
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1791 |
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1793 |
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1881 |
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1883 |
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=1960 |
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=1963 |
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2092 |
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2094 |
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2206 |
||||
# @@protoc_insertion_point(module_scope) |
@ -1,23 +1,41 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2022-2023 tecnovert |
||||
# Copyright (c) 2022-2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
from Crypto.Hash import RIPEMD160, SHA256 # pycryptodome |
||||
from basicswap.contrib.blake256.blake256 import blake_hash |
||||
|
||||
from Crypto.Hash import HMAC, RIPEMD160, SHA256, SHA512 # pycryptodome |
||||
|
||||
def sha256(data): |
||||
|
||||
def sha256(data: bytes) -> bytes: |
||||
h = SHA256.new() |
||||
h.update(data) |
||||
return h.digest() |
||||
|
||||
|
||||
def ripemd160(data): |
||||
def sha512(data: bytes) -> bytes: |
||||
h = SHA512.new() |
||||
h.update(data) |
||||
return h.digest() |
||||
|
||||
|
||||
def ripemd160(data: bytes) -> bytes: |
||||
h = RIPEMD160.new() |
||||
h.update(data) |
||||
return h.digest() |
||||
|
||||
|
||||
def hash160(s): |
||||
return ripemd160(sha256(s)) |
||||
def blake256(data: bytes) -> bytes: |
||||
return blake_hash(data) |
||||
|
||||
|
||||
def hash160(data: bytes) -> bytes: |
||||
return ripemd160(sha256(data)) |
||||
|
||||
|
||||
def hmac_sha512(secret: bytes, data: bytes) -> bytes: |
||||
h = HMAC.new(secret, digestmod=SHA512) |
||||
h.update(data) |
||||
return h.digest() |
||||
|
@ -0,0 +1,116 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Copyright (c) 2024 tecnovert |
||||
# Distributed under the MIT software license, see the accompanying |
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. |
||||
|
||||
from .crypto import blake256, hash160, hmac_sha512, ripemd160 |
||||
|
||||
from coincurve.keys import ( |
||||
PrivateKey, |
||||
PublicKey) |
||||
|
||||
|
||||
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes): |
||||
return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big')) |
||||
|
||||
|
||||
def hash160_dcr(data: bytes) -> bytes: |
||||
return ripemd160(blake256(data)) |
||||
|
||||
|
||||
class ExtKeyPair(): |
||||
__slots__ = ('_depth', '_fingerprint', '_child_no', '_chaincode', '_key', '_pubkey', 'hash_func') |
||||
|
||||
def __init__(self, coin_type=1): |
||||
if coin_type == 4: |
||||
self.hash_func = hash160_dcr |
||||
else: |
||||
self.hash_func = hash160 |
||||
|
||||
def set_seed(self, seed: bytes) -> None: |
||||
hashout: bytes = hmac_sha512(b'Bitcoin seed', seed) |
||||
self._key = hashout[:32] |
||||
self._pubkey = None |
||||
self._chaincode = hashout[32:] |
||||
self._depth = 0 |
||||
self._child_no = 0 |
||||
self._fingerprint = b'\0' * 4 |
||||
|
||||
def has_key(self) -> bool: |
||||
return False if self._key is None else True |
||||
|
||||
def neuter(self) -> None: |
||||
if self._key is None: |
||||
raise ValueError('Already neutered') |
||||
self._pubkey = PublicKey.from_secret(self._key).format() |
||||
self._key = None |
||||
|
||||
def derive(self, child_no: int): |
||||
out = ExtKeyPair() |
||||
out._depth = self._depth + 1 |
||||
out._child_no = child_no |
||||
|
||||
if (child_no >> 31) == 0: |
||||
if self._key: |
||||
K = PublicKey.from_secret(self._key) |
||||
k_encoded = K.format() |
||||
else: |
||||
K = PublicKey(self._pubkey) |
||||
k_encoded = self._pubkey |
||||
out._fingerprint = self.hash_func(k_encoded)[:4] |
||||
new_hash = BIP32Hash(self._chaincode, child_no, k_encoded[0], k_encoded[1:]) |
||||
out._chaincode = new_hash[32:] |
||||
|
||||
if self._key: |
||||
k = PrivateKey(self._key) |
||||
k.add(new_hash[:32], update=True) |
||||
out._key = k.secret |
||||
out._pubkey = None |
||||
else: |
||||
K.add(new_hash[:32], update=True) |
||||
out._key = None |
||||
out._pubkey = K.format() |
||||
else: |
||||
k = PrivateKey(self._key) |
||||
out._fingerprint = self.hash_func(self._pubkey if self._pubkey else PublicKey.from_secret(self._key).format())[:4] |
||||
new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key) |
||||
out._chaincode = new_hash[32:] |
||||
k.add(new_hash[:32], update=True) |
||||
out._key = k.secret |
||||
out._pubkey = None |
||||
|
||||
out.hash_func = self.hash_func |
||||
return out |
||||
|
||||
def encode_v(self) -> bytes: |
||||
return self._depth.to_bytes(1, 'big') + \ |
||||
self._fingerprint + \ |
||||
self._child_no.to_bytes(4, 'big') + \ |
||||
self._chaincode + \ |
||||
b'\x00' + \ |
||||
self._key |
||||
|
||||
def encode_p(self) -> bytes: |
||||
pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey |
||||
return self._depth.to_bytes(1, 'big') + \ |
||||
self._fingerprint + \ |
||||
self._child_no.to_bytes(4, 'big') + \ |
||||
self._chaincode + \ |
||||
pubkey |
||||
|
||||
def decode(self, data: bytes) -> None: |
||||
if len(data) != 74: |
||||
raise ValueError('Unexpected extkey length') |
||||
self._depth = data[0] |
||||
self._fingerprint = data[1:5] |
||||
self._child_no = int.from_bytes(data[5:9], 'big') |
||||
self._chaincode = data[9:41] |
||||
|
||||
if data[41] == 0: |
||||
self._key = data[42:] |
||||
self._pubkey = None |
||||
else: |
||||
self._key = None |
||||
self._pubkey = data[41:] |
@ -0,0 +1,16 @@ |
||||
monero_wallet: |
||||
image: i_decred_wallet |
||||
build: |
||||
context: decred_wallet |
||||
dockerfile: Dockerfile |
||||
container_name: decred_wallet |
||||
volumes: |
||||
- ${DATA_PATH}/decred_wallet:/data |
||||
expose: |
||||
- ${DCR_WALLET_RPC_PORT} |
||||
logging: |
||||
driver: "json-file" |
||||
options: |
||||
max-size: "10m" |
||||
max-file: "3" |
||||
restart: unless-stopped |
@ -0,0 +1,16 @@ |
||||
wownero_wallet: |
||||
image: i_wownero_wallet |
||||
build: |
||||
context: wownero_wallet |
||||
dockerfile: Dockerfile |
||||
container_name: wownero_wallet |
||||
volumes: |
||||
- ${DATA_PATH}/wownero_wallet:/data |
||||
expose: |
||||
- ${BASE_WOW_WALLET_PORT} |
||||
logging: |
||||
driver: "json-file" |
||||
options: |
||||
max-size: "10m" |
||||
max-file: "3" |
||||
restart: unless-stopped |
@ -0,0 +1,16 @@ |
||||
monero_daemon: |
||||
image: i_decred_daemon |
||||
build: |
||||
context: decred_daemon |
||||
dockerfile: Dockerfile |
||||
container_name: decred_daemon |
||||
volumes: |
||||
- ${DATA_PATH}/decred_daemon:/data |
||||
expose: |
||||
- ${DCR_RPC_PORT} |
||||
logging: |
||||
driver: "json-file" |
||||
options: |
||||
max-size: "10m" |
||||
max-file: "3" |
||||
restart: unless-stopped |
@ -0,0 +1,16 @@ |
||||
wownero_daemon: |
||||
image: i_wownero_daemon |
||||
build: |
||||
context: wownero_daemon |
||||
dockerfile: Dockerfile |
||||
container_name: wownero_daemon |
||||
volumes: |
||||
- ${DATA_PATH}/wownero_daemon:/data |
||||
expose: |
||||
- ${BASE_WOW_RPC_PORT} |
||||
logging: |
||||
driver: "json-file" |
||||
options: |
||||
max-size: "10m" |
||||
max-file: "3" |
||||
restart: unless-stopped |
@ -0,0 +1,25 @@ |
||||
FROM i_swapclient as install_stage |
||||
|
||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \ |
||||
find /coin_bin -name *.tar.gz -delete |
||||
|
||||
FROM debian:bullseye-slim |
||||
COPY --from=install_stage /coin_bin . |
||||
|
||||
ENV DCR_DATA /data |
||||
|
||||
RUN groupadd -r decred && useradd -r -m -g decred decred \ |
||||
&& apt-get update \ |
||||
&& apt-get install -qq --no-install-recommends gosu \ |
||||
&& rm -rf /var/lib/apt/lists/* \ |
||||
&& mkdir "$DCR_DATA" \ |
||||
&& chown -R decred:decred "$DCR_DATA" \ |
||||
&& ln -sfn "$DECRED_DATA" /home/decred/decred \ |
||||
&& chown -h decred:decred /home/decred/decred |
||||
VOLUME /data |
||||
|
||||
COPY entrypoint.sh /entrypoint.sh |
||||
ENTRYPOINT ["/entrypoint.sh"] |
||||
|
||||
EXPOSE 9108 9109 |
||||
CMD ["/decred/dcrd", "--datadir=/data"] |
@ -0,0 +1,11 @@ |
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
if [[ "$1" == "dcrctl" || "$1" == "dcrd" || "$1" == "dcrwallet" ]]; then |
||||
mkdir -p "$DECRED_DATA" |
||||
|
||||
chown -h decred:decred /home/decred/decred |
||||
exec gosu decred "$@" |
||||
else |
||||
exec "$@" |
||||
fi |
@ -0,0 +1,19 @@ |
||||
FROM i_decred_daemon |
||||
|
||||
ENV DCR_DATA /data |
||||
|
||||
RUN groupadd -r decred && useradd -r -m -g decred decred \ |
||||
&& apt-get update \ |
||||
&& apt-get install -qq --no-install-recommends gosu \ |
||||
&& rm -rf /var/lib/apt/lists/* \ |
||||
&& mkdir "$DCR_DATA" \ |
||||
&& chown -R decred:decred "$DCR_DATA" \ |
||||
&& ln -sfn "$DECRED_DATA" /home/decred/decred \ |
||||
&& chown -h decred:decred /home/decred/decred |
||||
VOLUME /data |
||||
|
||||
COPY entrypoint.sh /entrypoint.sh |
||||
ENTRYPOINT ["/entrypoint.sh"] |
||||
|
||||
EXPOSE 9209 |
||||
CMD ["/decred/dcrwallet", "--datadir=/data"] |
@ -0,0 +1,11 @@ |
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
if [[ "$1" == "dcrctl" || "$1" == "dcrd" || "$1" == "dcrwallet" ]]; then |
||||
mkdir -p "$DECRED_DATA" |
||||
|
||||
chown -h decred:decred /home/decred/decred |
||||
exec gosu decred "$@" |
||||
else |
||||
exec "$@" |
||||
fi |
@ -0,0 +1,24 @@ |
||||
FROM i_swapclient as install_stage |
||||
|
||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl |
||||
|
||||
FROM debian:bullseye-slim |
||||
|
||||
COPY --from=install_stage /coin_bin . |
||||
|
||||
ENV WOWNERO_DATA /data |
||||
|
||||
RUN groupadd -r wownero && useradd -r -m -g wownero wownero \ |
||||
&& apt-get update \ |
||||
&& apt-get install -qq --no-install-recommends gosu \ |
||||
&& rm -rf /var/lib/apt/lists/* \ |
||||
&& mkdir -p "$WOWNERO_DATA" \ |
||||
&& chown -R wownero:wownero "$WOWNERO_DATA" \ |
||||
&& ln -sfn "$WOWNERO_DATA" /home/wownero/.wownero \ |
||||
&& chown -h wownero:wownero /home/wownero/.wownero |
||||
VOLUME $WOWNERO_DATA |
||||
|
||||
COPY entrypoint.sh /entrypoint.sh |
||||
ENTRYPOINT ["/entrypoint.sh"] |
||||
|
||||
CMD ["/wownero/wownerod", "--non-interactive", "--config-file=/home/wownero/.wownero/wownerod.conf", "--confirm-external-bind"] |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue