Skip to content

Commit

Permalink
Transaction Signing in Blaze SDK (#30)
Browse files Browse the repository at this point in the history
* Fixed bug in script fee calc, added transaction signing util to SDK

* erroneous push

* fixed required signers collateral bug

* Fixed bug in script fee calc, added transaction signing util to SDK

* erroneous push

* fixed required signers collateral bug

* removed log

* changeset

* remove log

* remove docs
  • Loading branch information
EzePze authored Jun 4, 2024
1 parent 671150f commit 4b9211d
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 39 deletions.
6 changes: 6 additions & 0 deletions .changeset/green-jars-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blaze-cardano/sdk": patch
"@blaze-cardano/tx": patch
---

Fixed bug in script fee calc, added transaction signing util to SDK
5 changes: 5 additions & 0 deletions .changeset/heavy-lies-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blaze-cardano/tx": patch
---

Fixed collateral required signers bug
6 changes: 3 additions & 3 deletions packages/blaze-emulator/test/Emulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
VOID_PLUTUS_DATA,
alwaysTrueScript,
generateAccount,
generateGenesisUtxos,
generateGenesisOutputs,
signAndSubmit,
} from "./util";
import { Blaze, makeValue } from "@blaze-cardano/sdk";
Expand Down Expand Up @@ -50,8 +50,8 @@ describe("Emulator", () => {

beforeEach(() => {
emulator = new Emulator([
...generateGenesisUtxos(address1),
...generateGenesisUtxos(address2),
...generateGenesisOutputs(address1),
...generateGenesisOutputs(address2),
]);
provider = new EmulatorProvider(emulator);
wallet1 = new HotWallet(privateKeyHex1, NetworkId.Testnet, provider);
Expand Down
2 changes: 1 addition & 1 deletion packages/blaze-emulator/test/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const alwaysTrueScript = Script.newPlutusV2Script(
new PlutusV2Script(HexBlob("510100003222253330044a229309b2b2b9a1")),
);

export function generateGenesisUtxos(address: Address): TransactionOutput[] {
export function generateGenesisOutputs(address: Address): TransactionOutput[] {
return Array(10).fill(
new TransactionOutput(address, makeValue(1_000_000_000n)),
);
Expand Down
35 changes: 35 additions & 0 deletions packages/blaze-sdk/src/blaze.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Transaction } from "@blaze-cardano/core";
import { CborSet, VkeyWitness } from "@blaze-cardano/core";
import type { Provider } from "@blaze-cardano/query";
import { TxBuilder } from "@blaze-cardano/tx";
import type { Wallet } from "@blaze-cardano/wallet";
Expand Down Expand Up @@ -34,4 +36,37 @@ export class Blaze<ProviderType extends Provider, WalletType extends Wallet> {
.setChangeAddress(changeAddress)
.useEvaluator((x, y) => this.provider.evaluateTransaction(x, y));
}

/**
* Signs a transaction using the wallet.
* @param {Transaction} tx - The transaction to sign.
* @returns {Promise<Transaction>} - The signed transaction.
*/
async signTransaction(tx: Transaction): Promise<Transaction> {
const signed = await this.wallet.signTransaction(tx, true);
const ws = tx.witnessSet();
const vkeys = ws.vkeys()?.toCore() ?? [];

const signedKeys = signed.vkeys();
if (!signedKeys) {
throw new Error(
"signTransaction: no signed keys in wallet witness response",
);
}

if (
signedKeys.toCore().some(([vkey]) => vkeys.some(([key2]) => vkey == key2))
) {
throw new Error("signTransaction: some keys were already signed");
}

ws.setVkeys(
CborSet.fromCore(
[...signedKeys.toCore(), ...vkeys],
VkeyWitness.fromCore,
),
);
tx.setWitnessSet(ws);
return tx;
}
}
75 changes: 40 additions & 35 deletions packages/blaze-tx/src/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,8 @@ export class TxBuilder {
const exUnits = redeemer.exUnits();

// Calculate the fee contribution from this redeemer and add it to the total fee.
fee += this.params.prices.memory * Number(exUnits.steps());
fee += this.params.prices.steps * Number(exUnits.mem());
fee += this.params.prices.memory * Number(exUnits.mem());
fee += this.params.prices.steps * Number(exUnits.steps());
}

// Create a new Redeemers object and set its values to the updated redeemers.
Expand Down Expand Up @@ -727,26 +727,21 @@ export class TxBuilder {
tw.setPlutusV3Scripts(cborSet);
}
// Process vkey witnesses
const vkeyWitnesses = CborSet.fromCore([], VkeyWitness.fromCore);
const requiredWitnesses: VkeyWitness[] = [];
// const vkeyWitnesses = CborSet.fromCore([], VkeyWitness.fromCore);
const requiredWitnesses: [Ed25519PublicKeyHex, Ed25519SignatureHex][] = [];
this.requiredWitnesses.forEach((_) => {
requiredWitnesses.push(
VkeyWitness.fromCore([
Ed25519PublicKeyHex("0".repeat(64)),
Ed25519SignatureHex("0".repeat(128)),
]),
);
requiredWitnesses.push([
Ed25519PublicKeyHex("0".repeat(64)),
Ed25519SignatureHex("0".repeat(128)),
]);
});
for (let i = 0; i < this.additionalSigners; i++) {
requiredWitnesses.push(
VkeyWitness.fromCore([
Ed25519PublicKeyHex("0".repeat(64)),
Ed25519SignatureHex("0".repeat(128)),
]),
);
requiredWitnesses.push([
Ed25519PublicKeyHex("0".repeat(64)),
Ed25519SignatureHex("0".repeat(128)),
]);
}
vkeyWitnesses.setValues(requiredWitnesses);
tw.setVkeys(vkeyWitnesses);
tw.setVkeys(CborSet.fromCore(requiredWitnesses, VkeyWitness.fromCore));
tw.setRedeemers(this.redeemers);
// Process Plutus data
const plutusData = CborSet.fromCore([], PlutusData.fromCore);
Expand Down Expand Up @@ -990,6 +985,13 @@ export class TxBuilder {
const tis = CborSet.fromCore([], TransactionInput.fromCore);
tis.setValues([best.input()]);
this.body.setCollateral(tis);

const key = best.output().address().getProps().paymentPart!;
if (key.type == CredentialType.ScriptHash) {
this.requiredNativeScripts.add(key.hash);
} else {
this.requiredWitnesses.add(HashAsPubKeyHex(key.hash));
}
// Also set the collateral return to the output of the selected UTXO.
this.body.setCollateralReturn(best.output());
}
Expand Down Expand Up @@ -1110,7 +1112,7 @@ export class TxBuilder {
}
// Build the transaction witness set for fee estimation and script validation.
//excessValue = this.getPitch(false)
const tw = this.buildTransactionWitnessSet();
let tw = this.buildTransactionWitnessSet();
// Calculate and set the script data hash if necessary.
{
const scriptDataHash = this.getScriptDataHash(tw);
Expand All @@ -1122,38 +1124,41 @@ export class TxBuilder {
// Create a draft transaction for fee calculation.
const draft_tx = new Transaction(this.body, tw);
// Calculate and set the transaction fee.
const draft_size = draft_tx.toCbor().length / 2;
let draft_size = draft_tx.toCbor().length / 2;
this.calculateFees(draft_tx);
excessValue = value.merge(excessValue, new Value(-this.fee));
this.balanceChange(excessValue);
if (this.redeemers.size() > 0) {
this.prepareCollateral();
tw = this.buildTransactionWitnessSet();
const evaluationFee = await this.evaluate(draft_tx);
this.fee += evaluationFee;
excessValue = value.merge(excessValue, new Value(-evaluationFee));
tw.setRedeemers(this.redeemers);
draft_tx.setWitnessSet(tw);
}
{
const scriptDataHash = this.getScriptDataHash(tw);
if (scriptDataHash) {
this.body.setScriptDataHash(scriptDataHash);
}
}
this.body.setFee(this.fee);
if (this.body.collateral()) {
// Merge the fee with the excess value and rebalance the change.
this.balanceCollateralChange();
}
const final_size = draft_tx.toCbor().length / 2;
this.fee += BigInt(
Math.ceil((final_size - draft_size) * this.params.minFeeCoefficient),
);
excessValue = this.getPitch(false);
this.body.setFee(this.fee);
this.balanceChange(excessValue);
if (this.body.collateral()) {
this.balanceCollateralChange();
}

let final_size = draft_tx.toCbor().length / 2;
do {
this.fee += BigInt(
Math.ceil((final_size - draft_size) * this.params.minFeeCoefficient),
);
excessValue = this.getPitch(false);
this.body.setFee(this.fee);
this.balanceChange(excessValue);
if (this.body.collateral()) {
this.balanceCollateralChange();
}
draft_tx.setBody(this.body);
draft_size = final_size;
final_size = draft_tx.toCbor().length / 2;
} while (final_size != draft_size);
// Return the fully constructed transaction.
tw.setVkeys(CborSet.fromCore([], VkeyWitness.fromCore));
return new Transaction(this.body, tw);
Expand Down

0 comments on commit 4b9211d

Please sign in to comment.