actproof 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- actproof/__init__.py +234 -0
- actproof/anchor.py +586 -0
- actproof/canonical.py +369 -0
- actproof/catalogue.py +1031 -0
- actproof/cli.py +593 -0
- actproof/manifest.py +728 -0
- actproof/receipt.py +678 -0
- actproof/signers/__init__.py +89 -0
- actproof/signers/google_kms.py +392 -0
- actproof/signers/interface.py +298 -0
- actproof/signers/mnemonic.py +153 -0
- actproof/timestamp.py +527 -0
- actproof/verify.py +683 -0
- actproof-0.2.0.dist-info/METADATA +295 -0
- actproof-0.2.0.dist-info/RECORD +18 -0
- actproof-0.2.0.dist-info/WHEEL +4 -0
- actproof-0.2.0.dist-info/entry_points.txt +2 -0
- actproof-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Deyan Paroushev
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
Abstract base class for Algorand signers in actproof.
|
|
5
|
+
|
|
6
|
+
The structural invariant: every signer subclass signs ONLY Algorand
|
|
7
|
+
transactions, never arbitrary bytes. This module enforces that invariant
|
|
8
|
+
at class-definition time via ``__init_subclass__``; a subclass that
|
|
9
|
+
defines a method named ``sign_bytes`` (or any similar raw-byte path)
|
|
10
|
+
fails to construct, and the program that tried to import it fails to
|
|
11
|
+
start. The desired property is "security regression cannot land latent."
|
|
12
|
+
|
|
13
|
+
Why this matters
|
|
14
|
+
----------------
|
|
15
|
+
|
|
16
|
+
If the Ed25519 anchoring key is held in HSM-backed KMS, the operator
|
|
17
|
+
(the platform, the SRE on call) cannot extract the key material. KMS
|
|
18
|
+
itself, however, will happily sign any byte sequence if asked. The
|
|
19
|
+
defense-in-depth move is that the signer adapter, the only piece of
|
|
20
|
+
actproof code that holds a reference to the KMS client, MUST NEVER
|
|
21
|
+
expose a code path that calls the underlying sign with anything other
|
|
22
|
+
than a properly-built Algorand transaction.
|
|
23
|
+
|
|
24
|
+
A future refactor that adds ``sign_bytes`` because "it would be useful
|
|
25
|
+
for one thing" would silently expose every Algorand key the signer
|
|
26
|
+
holds. ``__init_subclass__`` makes that refactor impossible: the
|
|
27
|
+
module that defines the offending subclass fails to import, the test
|
|
28
|
+
suite fails red, the CI fails the deploy. The error is loud and
|
|
29
|
+
correct, instead of quiet and dangerous.
|
|
30
|
+
|
|
31
|
+
Concrete subclasses
|
|
32
|
+
-------------------
|
|
33
|
+
|
|
34
|
+
* ``MnemonicSigner`` (testing only) holds a 25-word mnemonic in process
|
|
35
|
+
memory.
|
|
36
|
+
* ``GoogleKMSSigner`` (production for GCP users) holds a reference to a
|
|
37
|
+
KMS key version resource path; the key itself stays in the HSM.
|
|
38
|
+
* User-supplied subclasses for AWS CloudHSM, Azure Key Vault, HashiCorp
|
|
39
|
+
Vault, etc. As long as the subclass extends ``AlgorandSigner`` and
|
|
40
|
+
implements ``address`` plus ``sign_transaction``, it satisfies the
|
|
41
|
+
contract.
|
|
42
|
+
|
|
43
|
+
Validation
|
|
44
|
+
----------
|
|
45
|
+
|
|
46
|
+
The ABC also provides ``validate_transaction(txn)`` which concrete
|
|
47
|
+
subclasses MUST call before signing. The default validation checks:
|
|
48
|
+
|
|
49
|
+
1. ``txn`` is an ``algosdk.transaction.Transaction`` instance.
|
|
50
|
+
2. ``txn.sender`` equals ``self.address``.
|
|
51
|
+
3. ``txn.receiver`` equals ``txn.sender`` (0-Algo self-payment pattern).
|
|
52
|
+
4. ``txn.amt`` equals zero.
|
|
53
|
+
5. ``txn.note`` is bytes and starts with the actproof ARC-2 prefix
|
|
54
|
+
(``b"actproof:j"``).
|
|
55
|
+
|
|
56
|
+
Subclasses can override ``validate_transaction`` to add additional
|
|
57
|
+
checks (fee bounds, network ID matching, etc.); they should ``super()``
|
|
58
|
+
into the base check or duplicate its logic.
|
|
59
|
+
|
|
60
|
+
References
|
|
61
|
+
----------
|
|
62
|
+
|
|
63
|
+
This module is the structural ancestor of every other signer in
|
|
64
|
+
actproof. The validation policy is the security ancestor of every
|
|
65
|
+
on-chain commitment the library produces.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
from __future__ import annotations
|
|
69
|
+
|
|
70
|
+
from abc import ABC, abstractmethod
|
|
71
|
+
from typing import Any, Final, FrozenSet
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"AlgorandSigner",
|
|
76
|
+
"FORBIDDEN_METHOD_NAMES",
|
|
77
|
+
"SignerValidationError",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ─────────────────────────────────────────────────────────────────
|
|
82
|
+
# FORBIDDEN METHOD NAMES
|
|
83
|
+
# ─────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
FORBIDDEN_METHOD_NAMES: Final[FrozenSet[str]] = frozenset({
|
|
86
|
+
"sign_bytes",
|
|
87
|
+
"sign_data",
|
|
88
|
+
"sign_raw",
|
|
89
|
+
"sign_message",
|
|
90
|
+
"sign_digest",
|
|
91
|
+
"sign",
|
|
92
|
+
"raw_sign",
|
|
93
|
+
"asymmetric_sign",
|
|
94
|
+
})
|
|
95
|
+
"""Method names that ``AlgorandSigner`` subclasses MUST NOT define.
|
|
96
|
+
|
|
97
|
+
Each represents a way to expose raw-byte signing on top of the typed
|
|
98
|
+
transaction signing path. The list is conservative: if a method name looks
|
|
99
|
+
like it could lure a future developer into bypassing the transaction-only
|
|
100
|
+
discipline, it is here.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ─────────────────────────────────────────────────────────────────
|
|
105
|
+
# EXCEPTIONS
|
|
106
|
+
# ─────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
class SignerValidationError(ValueError):
|
|
109
|
+
"""Raised by ``AlgorandSigner.validate_transaction`` on policy violation.
|
|
110
|
+
|
|
111
|
+
Subclass of ``ValueError`` so callers can catch ``ValueError`` if they
|
|
112
|
+
prefer unified handling, or ``SignerValidationError`` specifically to
|
|
113
|
+
distinguish signer-policy errors from other input-validation errors.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ─────────────────────────────────────────────────────────────────
|
|
118
|
+
# CONSTANTS USED IN DEFAULT VALIDATION
|
|
119
|
+
# ─────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
_ACTPROOF_NOTE_PREFIX: Final[bytes] = b"actproof:j"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ─────────────────────────────────────────────────────────────────
|
|
125
|
+
# THE ABSTRACT BASE CLASS
|
|
126
|
+
# ─────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
class AlgorandSigner(ABC):
|
|
129
|
+
"""Abstract Algorand transaction signer for actproof.
|
|
130
|
+
|
|
131
|
+
Concrete subclasses provide the cryptographic backend (mnemonic, KMS,
|
|
132
|
+
HSM, etc.). This base class enforces two structural rules at the level
|
|
133
|
+
of the class hierarchy:
|
|
134
|
+
|
|
135
|
+
1. The only signing-related method exposed on the public interface
|
|
136
|
+
is ``sign_transaction``, which accepts a fully-built
|
|
137
|
+
``algosdk.transaction.Transaction`` object.
|
|
138
|
+
2. Subclasses cannot add raw-byte signing methods. Defining any
|
|
139
|
+
method whose name appears in ``FORBIDDEN_METHOD_NAMES`` raises
|
|
140
|
+
``TypeError`` at the moment the subclass is defined (via
|
|
141
|
+
``__init_subclass__``). Adding such a method later in a refactor
|
|
142
|
+
causes the import of the module to fail, which is the desired
|
|
143
|
+
behaviour: the program does not start, rather than the security
|
|
144
|
+
regression sitting latent.
|
|
145
|
+
|
|
146
|
+
Subclasses MUST also implement:
|
|
147
|
+
|
|
148
|
+
* ``address`` (property) - the 58-character base32 Algorand address.
|
|
149
|
+
* ``sign_transaction(txn)`` - validate the transaction (via
|
|
150
|
+
``self.validate_transaction(txn)`` or equivalent) and return a
|
|
151
|
+
``SignedTransaction``.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init_subclass__(cls, **kwargs: object) -> None:
|
|
155
|
+
"""Reject any subclass that defines a forbidden method name.
|
|
156
|
+
|
|
157
|
+
Python calls this hook at the moment a subclass is created,
|
|
158
|
+
BEFORE any code that imports the subclass can use it. The check
|
|
159
|
+
inspects ``cls.__dict__`` (methods defined directly on this
|
|
160
|
+
subclass), not ``dir(cls)`` (which would also include inherited
|
|
161
|
+
methods). A subclass cannot accidentally inherit a forbidden
|
|
162
|
+
method from somewhere else and pass the check; the check is
|
|
163
|
+
about what THIS specific subclass introduces.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
TypeError: If any forbidden method name appears in the
|
|
167
|
+
subclass's own ``__dict__``. The error message names
|
|
168
|
+
every forbidden method actually found, plus lists the
|
|
169
|
+
full forbidden set so the developer can immediately see
|
|
170
|
+
which name they used and what the alternatives are.
|
|
171
|
+
"""
|
|
172
|
+
super().__init_subclass__(**kwargs)
|
|
173
|
+
forbidden_in_subclass = FORBIDDEN_METHOD_NAMES & set(cls.__dict__.keys())
|
|
174
|
+
if forbidden_in_subclass:
|
|
175
|
+
raise TypeError(
|
|
176
|
+
f"{cls.__name__} defines forbidden method(s) "
|
|
177
|
+
f"{sorted(forbidden_in_subclass)}. AlgorandSigner subclasses "
|
|
178
|
+
f"MUST NOT expose any raw-byte signing surface. The only "
|
|
179
|
+
f"signing method allowed on this interface is "
|
|
180
|
+
f"sign_transaction(txn) -> SignedTransaction. "
|
|
181
|
+
f"Complete list of forbidden method names: "
|
|
182
|
+
f"{sorted(FORBIDDEN_METHOD_NAMES)}."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
@abstractmethod
|
|
187
|
+
def address(self) -> str:
|
|
188
|
+
"""The 58-character base32 Algorand address this signer signs for.
|
|
189
|
+
|
|
190
|
+
Implementations may cache lazily (the first access triggers a key
|
|
191
|
+
derivation or remote call; subsequent accesses return the cached
|
|
192
|
+
value).
|
|
193
|
+
"""
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def sign_transaction(self, txn: Any) -> Any:
|
|
198
|
+
"""Sign an Algorand transaction.
|
|
199
|
+
|
|
200
|
+
Concrete implementations MUST call ``self.validate_transaction(txn)``
|
|
201
|
+
(or implement equivalent checks) before invoking the underlying
|
|
202
|
+
signing backend.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
txn: An ``algosdk.transaction.Transaction``, typically a
|
|
206
|
+
``PaymentTxn`` built by ``actproof.anchor.build_transaction``.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
An ``algosdk.transaction.SignedTransaction`` ready for
|
|
210
|
+
submission to algod.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
SignerValidationError: If the transaction violates the signing
|
|
214
|
+
policy (wrong sender, non-zero amount, missing or wrong
|
|
215
|
+
note prefix).
|
|
216
|
+
RuntimeError: If the underlying signing backend returns an
|
|
217
|
+
error.
|
|
218
|
+
"""
|
|
219
|
+
...
|
|
220
|
+
|
|
221
|
+
# ─────────────────────────────────────────────────────────────
|
|
222
|
+
# DEFAULT VALIDATION (subclasses MUST call this before signing)
|
|
223
|
+
# ─────────────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
def validate_transaction(self, txn: Any) -> None:
|
|
226
|
+
"""Validate an Algorand transaction against the actproof policy.
|
|
227
|
+
|
|
228
|
+
Concrete subclasses call this from inside ``sign_transaction``
|
|
229
|
+
before invoking the underlying signing backend. The default checks:
|
|
230
|
+
|
|
231
|
+
1. ``txn`` is an ``algosdk.transaction.Transaction`` instance.
|
|
232
|
+
2. ``txn.sender`` equals ``self.address``.
|
|
233
|
+
3. ``txn.receiver`` equals ``txn.sender`` (0-Algo self-payment).
|
|
234
|
+
4. ``txn.amt`` equals 0.
|
|
235
|
+
5. ``txn.note`` is bytes and starts with ``b"actproof:j"``.
|
|
236
|
+
|
|
237
|
+
Subclasses can extend by overriding this method (call ``super()``
|
|
238
|
+
first, then add extra checks).
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
txn: The transaction to validate.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
SignerValidationError: On the first policy violation found.
|
|
245
|
+
"""
|
|
246
|
+
# We avoid importing algosdk at module top to keep this file
|
|
247
|
+
# importable even if py-algorand-sdk has a transitive problem.
|
|
248
|
+
# The isinstance check happens dynamically.
|
|
249
|
+
try:
|
|
250
|
+
from algosdk.transaction import Transaction
|
|
251
|
+
except Exception as exc: # noqa: BLE001
|
|
252
|
+
raise SignerValidationError(
|
|
253
|
+
f"py-algorand-sdk not importable: {exc}"
|
|
254
|
+
) from exc
|
|
255
|
+
|
|
256
|
+
if not isinstance(txn, Transaction):
|
|
257
|
+
raise SignerValidationError(
|
|
258
|
+
f"Expected algosdk Transaction, got {type(txn).__name__}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# The sender must match this signer's address.
|
|
262
|
+
if getattr(txn, "sender", None) != self.address:
|
|
263
|
+
raise SignerValidationError(
|
|
264
|
+
f"Transaction sender {getattr(txn, 'sender', None)!r} does "
|
|
265
|
+
f"not match signer address {self.address!r}. The signer "
|
|
266
|
+
f"must own the sender's private key."
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Self-payment pattern: receiver equals sender.
|
|
270
|
+
if getattr(txn, "receiver", None) != self.address:
|
|
271
|
+
raise SignerValidationError(
|
|
272
|
+
f"Transaction receiver {getattr(txn, 'receiver', None)!r} "
|
|
273
|
+
f"does not equal sender (signer address {self.address!r}). "
|
|
274
|
+
f"actproof anchors use the 0-Algo self-payment pattern."
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Zero-value: actproof anchors never transfer Algos.
|
|
278
|
+
amount = getattr(txn, "amt", None)
|
|
279
|
+
if amount != 0:
|
|
280
|
+
raise SignerValidationError(
|
|
281
|
+
f"Transaction amount must be 0, got {amount}. actproof "
|
|
282
|
+
f"anchors are 0-Algo self-payments; the note field carries "
|
|
283
|
+
f"the commitment, not a value transfer."
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Note must start with the ARC-2 prefix.
|
|
287
|
+
note = getattr(txn, "note", None)
|
|
288
|
+
if not isinstance(note, (bytes, bytearray)):
|
|
289
|
+
raise SignerValidationError(
|
|
290
|
+
f"Transaction note must be bytes, got "
|
|
291
|
+
f"{type(note).__name__ if note is not None else 'None'}."
|
|
292
|
+
)
|
|
293
|
+
if not note.startswith(_ACTPROOF_NOTE_PREFIX):
|
|
294
|
+
raise SignerValidationError(
|
|
295
|
+
f"Transaction note must start with the actproof ARC-2 "
|
|
296
|
+
f"prefix {_ACTPROOF_NOTE_PREFIX!r}. Got prefix "
|
|
297
|
+
f"{bytes(note)[:32]!r}."
|
|
298
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Deyan Paroushev
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
MnemonicSigner: Algorand signer that holds a mnemonic in process memory.
|
|
5
|
+
|
|
6
|
+
**For testing only.**
|
|
7
|
+
|
|
8
|
+
The 25-word Algorand mnemonic encodes the full Ed25519 private key. Once
|
|
9
|
+
the mnemonic is in process memory, the private key is in process memory.
|
|
10
|
+
Anything that can read process memory (a debugger, a crash dump, a core
|
|
11
|
+
file, a kernel exploit, another process running as the same user) can
|
|
12
|
+
extract the key. Production anchoring keys belong in a hardware security
|
|
13
|
+
module: AWS CloudHSM, GCP KMS, Azure Key Vault, HashiCorp Vault, YubiHSM,
|
|
14
|
+
or similar.
|
|
15
|
+
|
|
16
|
+
This signer is appropriate for:
|
|
17
|
+
|
|
18
|
+
* Unit tests and CI smoke tests.
|
|
19
|
+
* Testnet experimentation during early development.
|
|
20
|
+
* Local proof-of-concept work.
|
|
21
|
+
* Documentation examples.
|
|
22
|
+
|
|
23
|
+
It is NOT appropriate for:
|
|
24
|
+
|
|
25
|
+
* Mainnet anchoring of compliance evidence.
|
|
26
|
+
* Any deployment where the signing key has material economic or legal
|
|
27
|
+
consequences attached.
|
|
28
|
+
|
|
29
|
+
On construction, the signer emits a ``UserWarning`` to make the testing-
|
|
30
|
+
only stance loud and discoverable. Tests that legitimately use the
|
|
31
|
+
mnemonic signer should suppress the warning with ``warnings.simplefilter``
|
|
32
|
+
or ``warnings.catch_warnings``; the warning is the signal, not the noise.
|
|
33
|
+
|
|
34
|
+
Example
|
|
35
|
+
-------
|
|
36
|
+
|
|
37
|
+
::
|
|
38
|
+
|
|
39
|
+
import warnings
|
|
40
|
+
from actproof.signers import MnemonicSigner
|
|
41
|
+
|
|
42
|
+
with warnings.catch_warnings():
|
|
43
|
+
warnings.simplefilter("ignore", category=UserWarning)
|
|
44
|
+
signer = MnemonicSigner(
|
|
45
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon "
|
|
46
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon "
|
|
47
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon "
|
|
48
|
+
"abandon"
|
|
49
|
+
)
|
|
50
|
+
# ... use signer for testnet anchoring
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import warnings
|
|
56
|
+
from typing import Any
|
|
57
|
+
|
|
58
|
+
from actproof.signers.interface import AlgorandSigner, SignerValidationError
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["MnemonicSigner"]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_MNEMONIC_WARNING_MESSAGE = (
|
|
65
|
+
"MnemonicSigner holds the Algorand private key in process memory and "
|
|
66
|
+
"is for testing only. Do NOT use for production anchoring. Production "
|
|
67
|
+
"keys belong in a hardware security module (AWS CloudHSM, GCP KMS, "
|
|
68
|
+
"Azure Key Vault, HashiCorp Vault, YubiHSM, or similar)."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MnemonicSigner(AlgorandSigner):
|
|
73
|
+
"""Algorand signer backed by an in-process mnemonic.
|
|
74
|
+
|
|
75
|
+
Holds the derived private key in process memory. **Testing only;**
|
|
76
|
+
see module docstring for the security caveats.
|
|
77
|
+
|
|
78
|
+
The address is derived from the mnemonic at construction time. Both
|
|
79
|
+
the private key and the address are cached for the lifetime of the
|
|
80
|
+
signer instance; there is no remote call.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
mnemonic_phrase: A 25-word Algorand mnemonic, words separated by
|
|
84
|
+
single spaces. Extra whitespace is stripped.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If the mnemonic is empty, not 25 words, or fails
|
|
88
|
+
algosdk's checksum.
|
|
89
|
+
RuntimeError: If py-algorand-sdk is not importable.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, mnemonic_phrase: str) -> None:
|
|
93
|
+
try:
|
|
94
|
+
from algosdk import account, mnemonic
|
|
95
|
+
except Exception as exc: # noqa: BLE001
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"py-algorand-sdk not importable: {exc}. "
|
|
98
|
+
f"Install with: pip install 'py-algorand-sdk>=2.6.1'"
|
|
99
|
+
) from exc
|
|
100
|
+
|
|
101
|
+
if not mnemonic_phrase or not mnemonic_phrase.strip():
|
|
102
|
+
raise ValueError("Mnemonic phrase is empty")
|
|
103
|
+
|
|
104
|
+
words = mnemonic_phrase.strip().split()
|
|
105
|
+
if len(words) != 25:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Algorand mnemonics are 25 words; got {len(words)}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
self._private_key: str = mnemonic.to_private_key(" ".join(words))
|
|
112
|
+
except Exception as exc: # noqa: BLE001
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Cannot derive private key from mnemonic: {exc}. "
|
|
115
|
+
f"Check the mnemonic words and checksum word."
|
|
116
|
+
) from exc
|
|
117
|
+
|
|
118
|
+
self._address: str = account.address_from_private_key(self._private_key)
|
|
119
|
+
|
|
120
|
+
# Loud warning on every instantiation. This is the security signal.
|
|
121
|
+
warnings.warn(
|
|
122
|
+
_MNEMONIC_WARNING_MESSAGE,
|
|
123
|
+
category=UserWarning,
|
|
124
|
+
stacklevel=2,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def address(self) -> str:
|
|
129
|
+
"""The signer's 58-character Algorand address."""
|
|
130
|
+
return self._address
|
|
131
|
+
|
|
132
|
+
def sign_transaction(self, txn: Any) -> Any:
|
|
133
|
+
"""Validate the transaction then sign it with the held private key.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
txn: An ``algosdk.transaction.Transaction`` to sign.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
An ``algosdk.transaction.SignedTransaction``.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
SignerValidationError: If the transaction violates the signing
|
|
143
|
+
policy (see ``AlgorandSigner.validate_transaction``).
|
|
144
|
+
"""
|
|
145
|
+
self.validate_transaction(txn)
|
|
146
|
+
try:
|
|
147
|
+
return txn.sign(self._private_key)
|
|
148
|
+
except SignerValidationError:
|
|
149
|
+
raise
|
|
150
|
+
except Exception as exc: # noqa: BLE001
|
|
151
|
+
raise RuntimeError(
|
|
152
|
+
f"algosdk transaction signing failed: {exc}"
|
|
153
|
+
) from exc
|