This repository was archived by the owner on Jan 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathmultisig.py
224 lines (174 loc) · 6.87 KB
/
multisig.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
"""
Example of how to use PyOTA's multisig feature.
This script will generate a multisig address using private keys from
three different seeds, prepare a bundle, sign the inputs, and then
finally broadcast the transactions to the Tangle.
References:
- https://github.com/iotaledger/wiki/blob/master/multisigs.md
"""
from typing import List
from iota import Address, Bundle, BundleValidator, ProposedTransaction, Tag, \
TransactionTrytes, TryteString
from iota.crypto.types import Digest, PrivateKey, Seed
from iota.multisig import MultisigIota
from iota.multisig.types import MultisigAddress
"""
Step 1: Each participant generates one or more digests that will be
used to create the multisig address.
For this example, we will create digests from 3 different private
keys, each owned by a different seed.
Note that this part normally happens on separate computers, so that
participants don't have to share their seeds.
"""
##
# Create digest 1 of 3.
#
api_1 = MultisigIota(
adapter='http://localhost:14265',
seed=Seed(
b'TESTVALUE9DONTUSEINPRODUCTION99999XKMYQP'
b'OIFGQSMIIWCQVMBSOKZASRQOFSIUSSHNDKVL9PJVS',
),
)
gd_result = api_1.get_digests(
# Starting key index.
index=0,
# Number of digests to generate.
count=1,
# Security level of the resulting digests.
# Must be a value between 1 (faster) and 3 (more secure).
security_level=3,
)
# ``get_digests`` returns a dict which contains 1 or more digests,
# depending on what value we used for ``count``.
digest_1 = gd_result['digests'][0] # type: Digest
##
# Create digest 2 of 3.
#
api_2 = MultisigIota(
adapter='http://localhost:14265',
seed=Seed(
b'TESTVALUE9DONTUSEINPRODUCTION99999DDWDKI'
b'FFBZVQHHINYDWRSMGGPZUERNLEAYMLFPHRXEWRNST',
),
)
# You can use any starting index that you want.
# For maximum security, each index should be used only once.
gd_result = api_2.get_digests(index=42, count=1, security_level=3)
digest_2 = gd_result['digests'][0] # type: Digest
##
# Create digest 3 of 3.
#
api_3 = MultisigIota(
adapter='http://localhost:14265',
seed=Seed(
b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI'
b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT',
),
)
# It is not necessary for every digest to have the same security level.
gd_result = api_3.get_digests(index=8, count=1, security_level=2)
digest_3 = gd_result['digests'][0] # type: Digest
"""
Step 2: Collect the digests and create a multisig address.
Note that digests are safe to share with other users, so this step is
typically performed on a single instance.
IMPORTANT: Keep track of the order that digests are used; you will
need to ensure that the same order is used to sign inputs!
"""
cma_result = api_1.create_multisig_address(
digests=[digest_1, digest_2, digest_3],
)
# For consistency, every API command returns a dict, even if it only
# has a single value.
multisig_address = cma_result['address'] # type: MultisigAddress
"""
Step 3: Prepare the bundle.
This step occurs some time later, after the multisig address has
received some IOTAs.
IMPORTANT: In IOTA, it is unsafe to spend from a single address
multiple times. Take care to spend ALL of the IOTAs controlled by the
multisig address, or generate a new multisig address that will receive
the change from the transaction!
"""
pmt_result = api_1.prepare_multisig_transfer(
# These are the transactions that will spend the IOTAs.
# You can divide up the IOTAs to send to multiple addresses if you
# want, but to keep this example focused, we will only include a
# single spend transaction.
transfers=[
ProposedTransaction(
address=Address(
b'TESTVALUE9DONTUSEINPRODUCTION99999NDGYBC'
b'QZJFGGWZ9GBQFKDOLWMVILARZRHJMSYFZETZTHTZR',
),
value=42,
# If you'd like, you may include an optional tag and/or
# message.
tag=Tag(b'KITTEHS'),
message=TryteString.from_string('thanx fur cheezburgers'),
),
],
# Specify our multisig address as the input for the spend
# transaction(s).
# Note that PyOTA currently only allows one multisig input per
# bundle (although the protocol itself does not impose a limit).
multisig_input=multisig_address,
# If there will be change from this transaction, you MUST specify
# the change address! Unlike regular transfers, multisig transfers
# will NOT automatically generate a change address; that wouldn't be
# fair to the other participants!
change_address=None,
)
prepared_trytes = pmt_result['trytes'] # type: List[TransactionTrytes]
"""
Step 4: Sign the inputs.
Note that we must apply signatures in the same order as when we created
the multisig address!
This step normally happens on separate computers, so that participants
don't have to share their private keys (except when doing m-of-n).
https://github.com/iotaledger/wiki/blob/master/multisigs.md#how-m-of-n-works
For this example, the structure of the bundle looks like this:
- Transaction 0: Spend IOTAs.
- Transactions 1-8: Transactions that will hold the signature
fragments for the multisig input:
- 1-3: Generated from ``digest_1`` (security level 3).
- 4-6: Generated from ``digest_2`` (security level 3).
- 7-8: Generated from ``digest_3`` (security level 2).
Note that transactions 1-8 don't have signatures yet; we need the
corresponding private keys in order to create those!
"""
bundle = Bundle.from_tryte_strings(prepared_trytes)
# Note that we must use the same parameters that we provided to the
# ``get_digests`` method, in order to generate the correct value to
# sign the input!
gpk_result = api_1.get_private_keys(index=0, count=1, security_level=3)
private_key_1 = gpk_result['keys'][0] # type: PrivateKey
private_key_1.sign_input_transactions(bundle, 1)
gpk_result = api_2.get_private_keys(index=42, count=1, security_level=3)
private_key_2 = gpk_result['keys'][0] # type: PrivateKey
private_key_2.sign_input_transactions(bundle, 4)
gpk_result = api_3.get_private_keys(index=8, count=1, security_level=2)
private_key_3 = gpk_result['keys'][0] # type: PrivateKey
private_key_3.sign_input_transactions(bundle, 7)
# Once we've applied the signatures, convert the Bundle back into tryte
# sequences so that we can broadcast them to the tangle.
signed_trytes = bundle.as_tryte_strings()
"""
Step 4.5 (Optional): Validate the signatures.
This step is purely optional. We are including it in this example
script so that you can see that the resulting signature fragments are
valid.
"""
validator = BundleValidator(bundle)
if not validator.is_valid():
raise ValueError(
'Bundle failed validation:\n{errors}'.format(
errors='\n'.join((' - ' + e) for e in validator.errors),
),
)
"""
Step 5: Broadcast the bundle.
Note that this step works the same as any other transfer.
"""
api_1.send_trytes(trytes=signed_trytes, depth=3)