Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit df4714e

Browse files
authored
Merge pull request #95 from mlouielu/add_pycurl_offset_length
Add offset and length to pycurl
2 parents 27f74d6 + b28b9d8 commit df4714e

File tree

2 files changed

+229
-12
lines changed

2 files changed

+229
-12
lines changed

iota/crypto/pycurl.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from typing import List, MutableSequence, Optional, Sequence
66

7+
from iota.exceptions import with_context
8+
79
__all__ = [
810
'Curl',
911
'HASH_LENGTH',
@@ -59,16 +61,35 @@ def reset(self):
5961
"""
6062
self._state = [0] * STATE_LENGTH # type: List[int]
6163

62-
def absorb(self, trits):
63-
# type: (Sequence[int], Optional[int]) -> None
64+
def absorb(self, trits, offset=0, length=None):
65+
# type: (Sequence[int], Optional[int], Optional[int]) -> None
6466
"""
6567
Absorb trits into the sponge.
6668
6769
:param trits:
6870
Sequence of trits to absorb.
71+
72+
:param offset:
73+
Starting offset in ``trits``.
74+
75+
:param length:
76+
Number of trits to absorb. Defaults to ``len(trits)``.
6977
"""
70-
length = len(trits)
71-
offset = 0
78+
pad = ((len(trits) % HASH_LENGTH) or HASH_LENGTH)
79+
trits += [0] * (HASH_LENGTH - pad)
80+
81+
if length is None:
82+
length = len(trits)
83+
84+
if length < 1:
85+
raise with_context(
86+
exc = ValueError('Invalid length passed to ``absorb``.'),
87+
context = {
88+
'trits': trits,
89+
'offset': offset,
90+
'length': length,
91+
},
92+
)
7293

7394
# Copy trits from ``trits`` into internal state, one hash at a
7495
# time, transforming internal state in between hashes.
@@ -92,14 +113,20 @@ def absorb(self, trits):
92113
# Move on to the next hash.
93114
offset += HASH_LENGTH
94115

95-
def squeeze(self, trits):
96-
# type: (MutableSequence[int]) -> None
116+
def squeeze(self, trits, offset=0, length=HASH_LENGTH):
117+
# type: (MutableSequence[int], Optional[int], Optional[int]) -> None
97118
"""
98119
Squeeze trits from the sponge.
99120
100121
:param trits:
101122
Sequence that the squeezed trits will be copied to.
102123
Note: this object will be modified!
124+
125+
:param offset:
126+
Starting offset in ``trits``.
127+
128+
:param length:
129+
Number of trits to squeeze, default to ``HASH_LENGTH``
103130
"""
104131
#
105132
# Squeeze is kind of like the opposite of absorb; it copies trits
@@ -110,14 +137,39 @@ def squeeze(self, trits):
110137
# can simplify the implementation somewhat.
111138
#
112139

113-
# Ensure that ``trits`` can hold at least one hash worth of trits.
114-
trits.extend([0] * max(0, HASH_LENGTH - len(trits)))
140+
# Ensure length can be mod by HASH_LENGTH
141+
if length % HASH_LENGTH != 0:
142+
raise with_context(
143+
exc = ValueError('Invalid length passed to ``sequeeze`.'),
144+
context = {
145+
'trits': trits,
146+
'offset': offset,
147+
'length': length,
148+
})
115149

116-
# Copy exactly one hash.
117-
trits[0:HASH_LENGTH] = self._state[0:HASH_LENGTH]
150+
# Ensure that ``trits`` can hold at least one hash worth of trits.
151+
trits.extend([0] * max(0, length - len(trits)))
152+
153+
# Check trits with offset can handle hash length
154+
if len(trits) - offset < HASH_LENGTH:
155+
raise with_context(
156+
exc = ValueError('Invalid offset passed to ``squeeze``.'),
157+
context = {
158+
'trits': trits,
159+
'offset': offset,
160+
'length': length
161+
},
162+
)
163+
164+
while length >= HASH_LENGTH:
165+
# Copy exactly one hash.
166+
trits[offset:offset + HASH_LENGTH] = self._state[0:HASH_LENGTH]
167+
168+
# One hash worth of trits copied; now transform.
169+
self._transform()
118170

119-
# One hash worth of trits copied; now transform.
120-
self._transform()
171+
offset += HASH_LENGTH
172+
length -= HASH_LENGTH
121173

122174
def _transform(self):
123175
# type: () -> None

test/crypto/pycurl_test.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# coding=utf-8
2+
from __future__ import absolute_import, division, print_function, \
3+
unicode_literals
4+
5+
from unittest import TestCase
6+
7+
from iota import TryteString
8+
from iota.crypto import Curl
9+
10+
11+
class TestCurl(TestCase):
12+
"""
13+
This is the test case for CURL-P hash function used in IOTA
14+
"""
15+
def test_correct_first(self):
16+
"""Test the inp tryte string will get the correct output"""
17+
inp = (
18+
'EMIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ'
19+
'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH'
20+
)
21+
22+
trits = TryteString(inp).as_trits()
23+
24+
curl = Curl()
25+
curl.absorb(trits)
26+
trits_out = []
27+
curl.squeeze(trits_out)
28+
29+
trits_out = TryteString.from_trits(trits_out)
30+
31+
self.assertEqual(
32+
trits_out,
33+
'AQBOPUMJMGVHFOXSMUAGZNACKUTISDPBSILMRAGIG'
34+
'RXXS9JJTLIKZUW9BCJWKSTFBDSBLNVEEGVGAMSSM')
35+
36+
def test_input_length_greater_than_243(self):
37+
"""Test input trytes length greater than hash length should work"""
38+
inp = (
39+
'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB'
40+
'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ'
41+
'9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA'
42+
)
43+
44+
trits = TryteString(inp).as_trits()
45+
46+
curl = Curl()
47+
curl.absorb(trits)
48+
trits_out = []
49+
curl.squeeze(trits_out)
50+
51+
trits_out = TryteString.from_trits(trits_out)
52+
53+
self.assertEqual(
54+
trits_out,
55+
'RWCBOLRFANOAYQWXXTFQJYQFAUTEEBSZWTIRSSDR'
56+
'EYGCNFRLHQVDZXYXSJKCQFQLJMMRHYAZKRRLQZDKR')
57+
58+
def test_input_without_offset(self):
59+
"""Test input without offset should work"""
60+
inp = (
61+
'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB'
62+
'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ'
63+
'9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA'
64+
)
65+
66+
trits = TryteString(inp).as_trits()
67+
68+
curl = Curl()
69+
curl.absorb(trits, 0, length=486)
70+
curl.absorb(trits, 0, length=243)
71+
trits_out = []
72+
curl.squeeze(trits_out)
73+
74+
trits_out = TryteString.from_trits(trits_out)
75+
76+
self.assertEqual(
77+
trits_out,
78+
'OTYHXEXJLCSMEY9LYCC9ASJXMORTLAYQEHRS9DAH'
79+
'9NR9DXLXYDGOVOBEL9LWRITLWPHPYPZDKXVPAPKUA')
80+
81+
def test_input_with_offset(self):
82+
"""Test input with offset should work"""
83+
inp = (
84+
'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB'
85+
'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ'
86+
'9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA'
87+
)
88+
89+
trits = TryteString(inp).as_trits()
90+
91+
curl = Curl()
92+
curl.absorb(trits, 243, length=486)
93+
curl.absorb(trits, 0, length=243)
94+
trits_out = []
95+
curl.squeeze(trits_out)
96+
97+
trits_out = TryteString.from_trits(trits_out)
98+
99+
self.assertEqual(
100+
trits_out,
101+
'ZWNF9YOCAKC9CXQFYZDKXSSAZOCAZLEVEB9OZDJQG'
102+
'WEULHUDY9RAWAT9GIUXTTUSYJEGNGQDVJCGTQLN9')
103+
104+
def test_squeeze_with_offset(self):
105+
"""Test squeeze with offset, this only used in ISS
106+
GitHub IRI ISS: https://github.com/iotaledger/iri/blob/dev/src/main/java/com/iota/iri/hash/ISS.java#L83
107+
"""
108+
inp = (
109+
'CDLFODMOGMQAWXDURDXTUAOO9BFESHYGZLBUWIIHPTLNZCUNHZAAXSUPUIBW'
110+
'IRLOVKCVWJSWEKRJQZUVRDZGZRNANUNCSGANCJWVHMZMVNJVUAZNFZKDAIVV'
111+
'LSMIM9SVGUHYECTGGIXTAMXXO9FIXUMQFZCGRQWAOWJPBTXNNQIRSTZEEAJV'
112+
'FSXWTHWBQJCWQNYYMHSPCYRA99ITVILYJPMFGOGOUOZUVABK9HMGABSORCVD'
113+
'FNGLMPJ9NFKBWCZMFPIWEAGRWPRNLLG9VYUUVLCTEWKGWQIRIJKERZWC9LVR'
114+
'XJEXNHBNUGEGGLMWGERKYFB9YEZCLXLKKMCGLRKQOGASDOUDYEDJLMV9BHPG'
115+
'GCXQIUVUOFFXKEIIINLVWLRYHHLKXPLSTWKIKNEJWEDFQQFXQVEHGRCIJC9T'
116+
'GVQNPPKGCFGPJNWSCPQZDDSIGAVZEIVYJDVPUOCTEMKTZFGXNGPQCOIBD9MX'
117+
'YTHJTX'
118+
)
119+
120+
121+
d = [0] * 243
122+
trits = TryteString(inp).as_trits()
123+
curl = Curl()
124+
125+
for i in range(6):
126+
curl.reset()
127+
curl.absorb(trits, i * 243, (i + 1) * 243)
128+
curl.squeeze(trits, i * 243)
129+
130+
curl.reset()
131+
curl.absorb(trits)
132+
curl.squeeze(d)
133+
134+
trits_out = TryteString.from_trits(d)
135+
136+
self.assertEqual(
137+
trits_out,
138+
'TAWDGNSEAD9ZRGBBVRVEKQYYVDOKHYQ9KEIYJKFT'
139+
'BQEYZDWZVMRFJQQGTMPHBZOGPIJCCVWLZVDKLAQVI')
140+
141+
def test_squeeze_with_486_length_should_work(self):
142+
"""
143+
Test squeeze with 486 length should work as well, no one use this
144+
in real situation
145+
"""
146+
inp = (
147+
'EMIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ'
148+
'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH'
149+
)
150+
151+
trits = TryteString(inp).as_trits()
152+
153+
curl = Curl()
154+
curl.absorb(trits)
155+
trits_out = []
156+
curl.squeeze(trits_out, length=486)
157+
158+
trits_out = TryteString.from_trits(trits_out)
159+
160+
self.assertEqual(
161+
trits_out,
162+
'AQBOPUMJMGVHFOXSMUAGZNACKUTISDPBSILMRAGIG'
163+
'RXXS9JJTLIKZUW9BCJWKSTFBDSBLNVEEGVGAMSSMQ'
164+
'GSJWCCFQRHWKTSMVPWWCEGOMCNWFYWDZBEDBLXIFB'
165+
'HOTCKUMCANLSXXTNKSYNBMOSDDEYFTDOYIKDRJM')

0 commit comments

Comments
 (0)