Skip to content

Commit 6d9e257

Browse files
authored
Merge pull request #516 from algorandfoundation/feat/gather-signatures
feat: return bytes from gatherSignatures to prevent double encoding
2 parents 136daf5 + 5aedc2d commit 6d9e257

File tree

8 files changed

+109
-131
lines changed

8 files changed

+109
-131
lines changed

docs/code/classes/types_composer.TransactionComposer.md

Lines changed: 58 additions & 58 deletions
Large diffs are not rendered by default.

docs/code/interfaces/types_composer.BuiltTransactions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Any `ABIMethod` objects associated with any of the transactions in a map keyed b
2424

2525
#### Defined in
2626

27-
[src/types/composer.ts:220](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L220)
27+
[src/types/composer.ts:218](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L218)
2828

2929
___
3030

@@ -36,7 +36,7 @@ Any `TransactionSigner` objects associated with any of the transactions in a map
3636

3737
#### Defined in
3838

39-
[src/types/composer.ts:222](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L222)
39+
[src/types/composer.ts:220](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L220)
4040

4141
___
4242

@@ -48,4 +48,4 @@ The built transactions
4848

4949
#### Defined in
5050

51-
[src/types/composer.ts:218](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L218)
51+
[src/types/composer.ts:216](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L216)

docs/code/modules/types_composer.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ and return the input error if it cannot or should not transform it.
344344

345345
#### Defined in
346346

347-
[src/types/composer.ts:157](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L157)
347+
[src/types/composer.ts:155](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L155)
348348

349349
___
350350

@@ -423,7 +423,7 @@ See algod API docs for more information: https://dev.algorand.co/reference/rest-
423423

424424
#### Defined in
425425

426-
[src/types/composer.ts:128](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L128)
426+
[src/types/composer.ts:126](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L126)
427427

428428
___
429429

@@ -435,7 +435,7 @@ All options to control a simulate request
435435

436436
#### Defined in
437437

438-
[src/types/composer.ts:134](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L134)
438+
[src/types/composer.ts:132](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L132)
439439

440440
___
441441

@@ -447,7 +447,7 @@ Options to control a simulate request, that does not require transaction signing
447447

448448
#### Defined in
449449

450-
[src/types/composer.ts:116](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L116)
450+
[src/types/composer.ts:114](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L114)
451451

452452
___
453453

@@ -464,7 +464,7 @@ ___
464464

465465
#### Defined in
466466

467-
[src/types/composer.ts:171](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L171)
467+
[src/types/composer.ts:169](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L169)
468468

469469
___
470470

@@ -488,4 +488,4 @@ Parameters to create an `TransactionComposer`.
488488

489489
#### Defined in
490490

491-
[src/types/composer.ts:191](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L191)
491+
[src/types/composer.ts:189](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/composer.ts#L189)

packages/transact/src/logicsig.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { Address, Addressable, concatArrays, decodeMsgpack, hash } from '@algorandfoundation/algokit-common'
22
import { MultisigAccount } from './multisig'
33
import { AddressWithDelegatedLsigSigner, TransactionSigner } from './signer'
4-
import { LogicSigSignature, MultisigSignature, SignedTransaction, encodeSignedTransaction } from './transactions/signed-transaction'
4+
import {
5+
LogicSigSignature,
6+
MultisigSignature,
7+
SignedTransaction,
8+
encodeSignedTransaction,
9+
validateSignedTransaction,
10+
} from './transactions/signed-transaction'
511
import { logicSigSignatureCodec } from './transactions/signed-transaction-meta'
612
import { Transaction } from './transactions/transaction'
713

@@ -137,6 +143,7 @@ export class LogicSigAccount extends LogicSig {
137143
stxn.authAddress = this.addr
138144
}
139145

146+
validateSignedTransaction(stxn)
140147
signedTxns.push(encodeSignedTransaction(stxn))
141148
}
142149

packages/transact/src/multisig.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ import {
88
PUBLIC_KEY_BYTE_LENGTH,
99
SIGNATURE_BYTE_LENGTH,
1010
} from '@algorandfoundation/algokit-common'
11+
import { DelegatedLsigSigner } from './logicsig'
1112
import { AddressWithDelegatedLsigSigner, AddressWithTransactionSigner, TransactionSigner } from './signer'
1213
import {
1314
decodeSignedTransaction,
1415
encodeSignedTransaction,
1516
MultisigSignature,
1617
MultisigSubsignature,
1718
SignedTransaction,
19+
validateSignedTransaction,
1820
} from './transactions/signed-transaction'
1921
import { Transaction } from './transactions/transaction'
20-
import { DelegatedLsigSigner } from './logicsig'
2122

2223
const toPublicKeys = (addrs: Array<string | Address>): Uint8Array[] => addrs.map((addr) => getAddress(addr).publicKey)
2324

@@ -451,7 +452,10 @@ export class MultisigAccount implements AddressWithTransactionSigner, AddressWit
451452
signedMsigTxns.push(signedMsigTxn)
452453
}
453454

454-
return signedMsigTxns.map(encodeSignedTransaction)
455+
return signedMsigTxns.map((stxn) => {
456+
validateSignedTransaction(stxn)
457+
return encodeSignedTransaction(stxn)
458+
})
455459
}
456460

457461
this._lsigSigner = async (lsig, _) => {

packages/transact/src/signer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Address, Addressable, concatArrays, Expand, ReadableAddress } from '@algorandfoundation/algokit-common'
2-
import { encodeTransaction, Transaction } from './transactions/transaction'
32
import { DelegatedLsigSigner, ProgramDataSigner } from './logicsig'
4-
import { encodeSignedTransaction, SignedTransaction } from './transactions/signed-transaction'
3+
import { encodeSignedTransaction, SignedTransaction, validateSignedTransaction } from './transactions/signed-transaction'
4+
import { encodeTransaction, Transaction } from './transactions/transaction'
55

66
/** Function for signing a group of transactions */
77
export type TransactionSigner = (txnGroup: Transaction[], indexesToSign: number[]) => Promise<Uint8Array[]>
@@ -70,7 +70,10 @@ export function generateAddressWithSigners(args: {
7070
stxns.push(stxn)
7171
}
7272

73-
return stxns.map(encodeSignedTransaction)
73+
return stxns.map((stxn) => {
74+
validateSignedTransaction(stxn)
75+
return encodeSignedTransaction(stxn)
76+
})
7477
}
7578

7679
const lsigSigner: DelegatedLsigSigner = async (lsig, msig) => {
@@ -105,6 +108,7 @@ export function makeEmptyTransactionSigner(): TransactionSigner {
105108
txn: txnGroup[index],
106109
sig: new Uint8Array(64).fill(0),
107110
}
111+
validateSignedTransaction(stxn)
108112
unsigned.push(encodeSignedTransaction(stxn))
109113
}
110114

src/types/composer.spec.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,7 @@ describe('TransactionComposer', () => {
485485
const signedTxns = await composer.gatherSignatures()
486486

487487
expect(signedTxns).toHaveLength(1)
488-
expect(signedTxns[0]).toBeDefined()
489-
expect(signedTxns[0].sig).toBeDefined()
488+
expect(signedTxns[0].length).toBeGreaterThan(0)
490489
})
491490

492491
test('should successfully sign multiple transactions with the same signer', async () => {
@@ -508,8 +507,8 @@ describe('TransactionComposer', () => {
508507
const signedTxns = await composer.gatherSignatures()
509508

510509
expect(signedTxns).toHaveLength(2)
511-
expect(signedTxns[0].sig).toBeDefined()
512-
expect(signedTxns[1].sig).toBeDefined()
510+
expect(signedTxns[0].length).toBeGreaterThan(0)
511+
expect(signedTxns[1].length).toBeGreaterThan(0)
513512
})
514513

515514
test('should successfully sign transactions with multiple different signers', async () => {
@@ -532,8 +531,8 @@ describe('TransactionComposer', () => {
532531
const signedTxns = await composer.gatherSignatures()
533532

534533
expect(signedTxns).toHaveLength(2)
535-
expect(signedTxns[0].sig).toBeDefined()
536-
expect(signedTxns[1].sig).toBeDefined()
534+
expect(signedTxns[0].length).toBeGreaterThan(0)
535+
expect(signedTxns[1].length).toBeGreaterThan(0)
537536
})
538537

539538
test('should throw error when no transactions to sign', async () => {
@@ -613,25 +612,5 @@ describe('TransactionComposer', () => {
613612

614613
await expect(composer.gatherSignatures()).rejects.toThrow('Transactions at indexes [0] were not signed')
615614
})
616-
617-
test('should throw error when signer returns invalid signed transaction data', async () => {
618-
const { algorand, context } = fixture
619-
const sender = context.testAccount
620-
621-
// Create a faulty signer that returns invalid data
622-
const faultySigner: TransactionSigner = async (_group, indexes) => {
623-
return indexes.map(() => new Uint8Array([1, 2, 3]))
624-
}
625-
626-
const composer = algorand.newGroup()
627-
composer.addPayment({
628-
sender,
629-
receiver: sender,
630-
amount: AlgoAmount.MicroAlgo(1000),
631-
signer: faultySigner,
632-
})
633-
634-
await expect(composer.gatherSignatures()).rejects.toThrow('Invalid signed transaction at index 0')
635-
})
636615
})
637616
})

src/types/composer.ts

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ import {
1616
TransactionType,
1717
assignFee,
1818
calculateFee,
19-
decodeSignedTransaction,
19+
decodeSignedTransactions,
2020
decodeTransaction,
21-
encodeSignedTransactions,
2221
encodeTransactionRaw,
2322
groupTransactions,
2423
makeEmptyTransactionSigner,
25-
validateSignedTransaction,
2624
validateTransaction,
2725
} from '@algorandfoundation/algokit-transact'
2826
import { Buffer } from 'buffer'
@@ -250,7 +248,7 @@ export class TransactionComposer {
250248

251249
private transactionsWithSigners?: TransactionWithSigner[]
252250

253-
private signedTransactions?: SignedTransaction[]
251+
private signedTransactions?: Uint8Array[]
254252

255253
// Cache the raw transactions before resource population for error handling
256254
private rawBuildTransactions?: Transaction[]
@@ -1858,19 +1856,18 @@ export class TransactionComposer {
18581856
})
18591857
}
18601858

1861-
const group = this.signedTransactions[0].txn.group
1859+
const group = this.transactionsWithSigners[0].txn.group
18621860

18631861
let waitRounds = params?.maxRoundsToWaitForConfirmation
18641862

18651863
if (waitRounds === undefined) {
18661864
const suggestedParams = await this.getSuggestedParams()
18671865
const firstRound = suggestedParams.firstValid
1868-
const lastRound = this.signedTransactions.reduce((max, txn) => (txn.txn.lastValid > max ? txn.txn.lastValid : max), 0n)
1866+
const lastRound = this.transactionsWithSigners.reduce((max, txn) => (txn.txn.lastValid > max ? txn.txn.lastValid : max), 0n)
18691867
waitRounds = Number(lastRound - firstRound) + 1
18701868
}
18711869

1872-
const encodedTxns = encodeSignedTransactions(this.signedTransactions)
1873-
await this.algod.sendRawTransaction(encodedTxns)
1870+
await this.algod.sendRawTransaction(this.signedTransactions)
18741871

18751872
if (transactionsToSend.length > 1 && group) {
18761873
Config.getLogger(params?.suppressLog).verbose(
@@ -1927,7 +1924,9 @@ export class TransactionComposer {
19271924
txn,
19281925
signer: makeEmptyTransactionSigner(),
19291926
}))
1930-
const signedTransactions = await this.signTransactions(transactionsWithEmptySigners)
1927+
const encodedSignedTransactions = await this.signTransactions(transactionsWithEmptySigners)
1928+
const signedTransactions = decodeSignedTransactions(encodedSignedTransactions)
1929+
19311930
const simulateResponse = await this.algod.simulateTransactions({
19321931
txnGroups: [{ txns: signedTransactions }],
19331932
allowEmptySignatures: true,
@@ -2030,7 +2029,8 @@ export class TransactionComposer {
20302029
}
20312030

20322031
const transactions = transactionsWithSigner.map((e) => e.txn)
2033-
const signedTransactions = await this.signTransactions(transactionsWithSigner)
2032+
const encodedSignedTransactions = await this.signTransactions(transactionsWithSigner)
2033+
const signedTransactions = decodeSignedTransactions(encodedSignedTransactions)
20342034

20352035
const simulateRequest = {
20362036
txnGroups: [
@@ -2097,7 +2097,7 @@ export class TransactionComposer {
20972097
return encoder.encode(arc2Payload)
20982098
}
20992099

2100-
public async gatherSignatures(): Promise<SignedTransaction[]> {
2100+
public async gatherSignatures(): Promise<Uint8Array[]> {
21012101
if (this.signedTransactions) {
21022102
return this.signedTransactions
21032103
}
@@ -2112,7 +2112,7 @@ export class TransactionComposer {
21122112
return this.signedTransactions
21132113
}
21142114

2115-
private async signTransactions(transactionsWithSigners: TransactionWithSigner[]): Promise<SignedTransaction[]> {
2115+
private async signTransactions(transactionsWithSigners: TransactionWithSigner[]): Promise<Uint8Array[]> {
21162116
if (transactionsWithSigners.length === 0) {
21172117
throw new Error('No transactions available to sign')
21182118
}
@@ -2132,40 +2132,24 @@ export class TransactionComposer {
21322132
const signedGroups = await Promise.all(signerEntries.map(([signer, indexes]) => signer(transactions, indexes)))
21332133

21342134
// Reconstruct signed transactions in original order
2135-
const rawSignedTransactions: (Uint8Array | null)[] = new Array(transactionsWithSigners.length).fill(null)
2135+
const encodedSignedTransactions: (Uint8Array | null)[] = new Array(transactionsWithSigners.length).fill(null)
21362136
signerEntries.forEach(([, indexes], signerIndex) => {
21372137
const stxs = signedGroups[signerIndex]
21382138
indexes.forEach((txIndex, stxIndex) => {
2139-
rawSignedTransactions[txIndex] = stxs[stxIndex] ?? null
2139+
encodedSignedTransactions[txIndex] = stxs[stxIndex] ?? null
21402140
})
21412141
})
21422142

21432143
// Verify all transactions were signed
2144-
const unsignedIndexes = rawSignedTransactions
2144+
const unsignedIndexes = encodedSignedTransactions
21452145
.map((stxn, index) => (stxn == null ? index : null))
21462146
.filter((index): index is number => index !== null)
21472147

21482148
if (unsignedIndexes.length > 0) {
21492149
throw new Error(`Transactions at indexes [${unsignedIndexes.join(', ')}] were not signed`)
21502150
}
21512151

2152-
// Decode and validate all signed transactions
2153-
const signedTransactions = rawSignedTransactions.map((stxn, index) => {
2154-
if (stxn == null) {
2155-
// This shouldn't happen due to the check above, but ensures type safety
2156-
throw new Error(`Transaction at index ${index} was not signed`)
2157-
}
2158-
2159-
try {
2160-
const signedTransaction = decodeSignedTransaction(stxn)
2161-
validateSignedTransaction(signedTransaction)
2162-
return signedTransaction
2163-
} catch (err) {
2164-
throw new Error(`Invalid signed transaction at index ${index}. ${err}`)
2165-
}
2166-
})
2167-
2168-
return signedTransactions
2152+
return encodedSignedTransactions as Uint8Array[] // The guard above ensures no nulls
21692153
}
21702154

21712155
private parseAbiReturnValues(confirmations: PendingTransactionResponse[]): ABIReturn[] {

0 commit comments

Comments
 (0)