Skip to content

Commit 91cd7a4

Browse files
committed
Add 'build_and_sign' to tx builder
1 parent 14e9a4f commit 91cd7a4

File tree

4 files changed

+89
-97
lines changed

4 files changed

+89
-97
lines changed

integration-test/test/test_all.py

Lines changed: 7 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -152,36 +152,9 @@ def load_or_create_key_pair(base_dir, base_name):
152152
nft_output = TransactionOutput(address, Value(min_val, my_nft))
153153
builder.add_output(nft_output)
154154

155-
# Build a finalized transaction body with the change returning to our own address
156-
tx_body = builder.build(change_address=address)
157-
158-
"""Sign transaction and add witnesses"""
159-
# Sign the transaction body hash using the payment signing key
160-
payment_signature = self.payment_skey.sign(tx_body.hash())
161-
162-
# Sign the transaction body hash using the extended payment signing key
163-
extended_payment_signature = self.extended_payment_skey.sign(tx_body.hash())
164-
165-
# Sign the transaction body hash using the policy signing key because we are minting new tokens
166-
policy_signature = policy_skey.sign(tx_body.hash())
167-
168-
# Add verification keys and their signatures to the witness set
169-
vk_witnesses = [
170-
VerificationKeyWitness(self.payment_vkey, payment_signature),
171-
VerificationKeyWitness(policy_vkey, policy_signature),
172-
VerificationKeyWitness(
173-
self.extended_payment_vkey, extended_payment_signature
174-
),
175-
]
176-
177-
# Create final signed transaction
178-
signed_tx = Transaction(
179-
tx_body,
180-
# Beside vk witnesses, We also need to add the policy script to witness set when we are minting new tokens.
181-
TransactionWitnessSet(
182-
vkey_witnesses=vk_witnesses, native_scripts=native_scripts
183-
),
184-
auxiliary_data=auxiliary_data,
155+
# Build and sign transaction
156+
signed_tx = builder.build_and_sign(
157+
[self.payment_skey, self.extended_payment_skey, policy_skey], address
185158
)
186159

187160
print("############### Transaction created ###############")
@@ -217,23 +190,8 @@ def load_or_create_key_pair(base_dir, base_name):
217190

218191
builder.add_output(nft_to_send)
219192

220-
tx_body = builder.build(change_address=address)
221-
222-
"""Sign transaction and add witnesses"""
223-
# Sign the transaction body hash using the payment signing key
224-
payment_signature = self.payment_skey.sign(tx_body.hash())
225-
226-
# Add verification keys and their signatures to the witness set
227-
vk_witnesses = [
228-
VerificationKeyWitness(self.payment_vkey, payment_signature),
229-
]
230-
231193
# Create final signed transaction
232-
signed_tx = Transaction(
233-
tx_body,
234-
# Beside vk witnesses, We also need to add the policy script to witness set when we are minting new tokens.
235-
TransactionWitnessSet(vkey_witnesses=vk_witnesses),
236-
)
194+
signed_tx = builder.build_and_sign([self.payment_skey], address)
237195

238196
print("############### Transaction created ###############")
239197
print(signed_tx)
@@ -277,19 +235,7 @@ def test_plutus(self):
277235
TransactionOutput(script_address, 50000000, datum_hash=datum_hash(datum))
278236
)
279237

280-
tx_body = builder.build(change_address=giver_address)
281-
witness_set = builder.build_witness_set()
282-
283-
payment_signature = self.payment_skey.sign(tx_body.hash())
284-
285-
witness_set.vkey_witnesses = [
286-
VerificationKeyWitness(self.payment_vkey, payment_signature),
287-
]
288-
289-
signed_tx = Transaction(
290-
tx_body,
291-
witness_set,
292-
)
238+
signed_tx = builder.build_and_sign([self.payment_skey], giver_address)
293239

294240
print("############### Transaction created ###############")
295241
print(signed_tx)
@@ -307,19 +253,7 @@ def test_plutus(self):
307253
builder.add_input_address(giver_address)
308254
builder.add_output(TransactionOutput(taker_address, 5000000))
309255

310-
tx_body = builder.build(change_address=giver_address)
311-
witness_set = builder.build_witness_set()
312-
313-
payment_signature = self.payment_skey.sign(tx_body.hash())
314-
315-
witness_set.vkey_witnesses = [
316-
VerificationKeyWitness(self.payment_vkey, payment_signature),
317-
]
318-
319-
signed_tx = Transaction(
320-
tx_body,
321-
witness_set,
322-
)
256+
signed_tx = builder.build_and_sign([self.payment_skey], giver_address)
323257

324258
print("############### Transaction created ###############")
325259
print(signed_tx)
@@ -352,19 +286,7 @@ def test_plutus(self):
352286

353287
builder.collaterals.append(non_nft_utxo)
354288

355-
tx_body = builder.build(change_address=taker_address)
356-
witness_set = builder.build_witness_set()
357-
358-
collateral_signature = self.extended_payment_skey.sign(tx_body.hash())
359-
360-
witness_set.vkey_witnesses = [
361-
VerificationKeyWitness(self.extended_payment_vkey, collateral_signature),
362-
]
363-
364-
signed_tx = Transaction(
365-
tx_body,
366-
witness_set,
367-
)
289+
signed_tx = builder.build_and_sign([self.extended_payment_skey], taker_address)
368290

369291
print("############### Transaction created ###############")
370292
print(signed_tx)

pycardano/key.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ def sign(self, data: bytes) -> bytes:
143143
signed_message = NACLSigningKey(self.payload).sign(data)
144144
return signed_message.signature
145145

146+
def to_verification_key(self) -> VerificationKey:
147+
verification_key = NACLSigningKey(self.payload).verify_key
148+
return VerificationKey(
149+
bytes(verification_key),
150+
self.key_type.replace("Signing", "Verification"),
151+
self.description.replace("Signing", "Verification"),
152+
)
153+
146154
@classmethod
147155
def generate(cls) -> SigningKey:
148156
signing_key = PrivateKey.generate()
@@ -162,19 +170,21 @@ def hash(self) -> VerificationKeyHash:
162170

163171
@classmethod
164172
def from_signing_key(cls, key: SigningKey) -> VerificationKey:
165-
verification_key = NACLSigningKey(bytes(key)).verify_key
166-
return cls(
167-
bytes(verification_key),
168-
key.key_type.replace("Signing", "Verification"),
169-
key.description.replace("Signing", "Verification"),
170-
)
173+
return key.to_verification_key()
171174

172175

173176
class ExtendedSigningKey(Key):
174177
def sign(self, data: bytes) -> bytes:
175178
private_key = BIP32ED25519PrivateKey(self.payload[:64], self.payload[96:])
176179
return private_key.sign(data)
177180

181+
def to_verification_key(self) -> ExtendedVerificationKey:
182+
return ExtendedVerificationKey(
183+
self.payload[64:],
184+
self.key_type.replace("Signing", "Verification"),
185+
self.description.replace("Signing", "Verification"),
186+
)
187+
178188

179189
class ExtendedVerificationKey(Key):
180190
def hash(self) -> VerificationKeyHash:
@@ -187,11 +197,7 @@ def hash(self) -> VerificationKeyHash:
187197

188198
@classmethod
189199
def from_signing_key(cls, key: ExtendedSigningKey) -> ExtendedVerificationKey:
190-
return cls(
191-
key.payload[64:],
192-
key.key_type.replace("Signing", "Verification"),
193-
key.description.replace("Signing", "Verification"),
194-
)
200+
return key.to_verification_key()
195201

196202
def to_non_extended(self) -> VerificationKey:
197203
"""Get the 32-byte verification with chain code trimmed off

pycardano/txbuilder.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
UTxOSelectionException,
1919
)
2020
from pycardano.hash import ScriptDataHash, ScriptHash, VerificationKeyHash
21-
from pycardano.key import VerificationKey
21+
from pycardano.key import ExtendedSigningKey, SigningKey, VerificationKey
2222
from pycardano.logging import logger
2323
from pycardano.metadata import AuxiliaryData
2424
from pycardano.nativescript import NativeScript, ScriptAll, ScriptAny, ScriptPubkey
@@ -646,3 +646,33 @@ def build(self, change_address: Optional[Address] = None) -> TransactionBody:
646646
tx_body = self._build_tx_body()
647647

648648
return tx_body
649+
650+
def build_and_sign(
651+
self,
652+
signing_keys: List[Union[SigningKey, ExtendedSigningKey]],
653+
change_address: Optional[Address] = None,
654+
) -> Transaction:
655+
"""Build a transaction body from all constraints set through the builder and sign the transaction with
656+
provided signing keys.
657+
658+
Args:
659+
signing_keys (List[Union[SigningKey, ExtendedSigningKey]]): A list of signing keys that will be used to
660+
sign the transaction.
661+
change_address (Optional[Address]): Address to which changes will be returned. If not provided, the
662+
transaction body will likely be unbalanced (sum of inputs is greater than the sum of outputs).
663+
664+
Returns:
665+
Transaction: A signed transaction.
666+
"""
667+
668+
tx_body = self.build(change_address=change_address)
669+
witness_set = self.build_witness_set()
670+
witness_set.vkey_witnesses = []
671+
672+
for signing_key in signing_keys:
673+
signature = signing_key.sign(tx_body.hash())
674+
witness_set.vkey_witnesses.append(
675+
VerificationKeyWitness(signing_key.to_verification_key(), signature)
676+
)
677+
678+
return Transaction(tx_body, witness_set, auxiliary_data=self.auxiliary_data)

test/pycardano/test_txbuilder.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import replace
2+
from test.pycardano.test_key import SK
23
from test.pycardano.util import chain_context
34

45
import cbor2
@@ -23,6 +24,7 @@
2324
from pycardano.plutus import ExecutionUnits, PlutusData, Redeemer, RedeemerTag
2425
from pycardano.transaction import MultiAsset, TransactionInput, TransactionOutput, UTxO
2526
from pycardano.txbuilder import TransactionBuilder
27+
from pycardano.witness import VerificationKeyWitness
2628

2729

2830
def test_tx_builder(chain_context):
@@ -371,3 +373,35 @@ def test_excluded_input(chain_context):
371373
}
372374

373375
assert expected == tx_body.to_primitive()
376+
377+
378+
def test_build_and_sign(chain_context):
379+
sender = "addr_test1vrm9x2zsux7va6w892g38tvchnzahvcd9tykqf3ygnmwtaqyfg52x"
380+
sender_address = Address.from_primitive(sender)
381+
382+
tx_builder1 = TransactionBuilder(
383+
chain_context, [RandomImproveMultiAsset([0, 0, 0, 0, 0])]
384+
)
385+
tx_builder1.add_input_address(sender).add_output(
386+
TransactionOutput.from_primitive([sender, 500000])
387+
)
388+
389+
tx_body = tx_builder1.build(change_address=sender_address)
390+
391+
tx_builder2 = TransactionBuilder(
392+
chain_context, [RandomImproveMultiAsset([0, 0, 0, 0, 0])]
393+
)
394+
tx_builder2.add_input_address(sender).add_output(
395+
TransactionOutput.from_primitive([sender, 500000])
396+
)
397+
tx = tx_builder2.build_and_sign([SK], change_address=sender_address)
398+
399+
assert tx.transaction_witness_set.vkey_witnesses == [
400+
VerificationKeyWitness(SK.to_verification_key(), SK.sign(tx_body.hash()))
401+
]
402+
assert (
403+
"a300818258203131313131313131313131313131313131313131313131313131313131313131"
404+
"00018282581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a0007"
405+
"a12082581d60f6532850e1bccee9c72a9113ad98bcc5dbb30d2ac960262444f6e5f41a004223"
406+
"a3021a0002867d" == tx_body.to_cbor()
407+
)

0 commit comments

Comments
 (0)