@@ -28,6 +28,7 @@ import * as querystring from 'querystring';
2828import { TransactionBuilderFactory } from './lib' ;
2929import { KeyPair as CantonKeyPair } from './lib/keyPair' ;
3030import utils from './lib/utils' ;
31+ import { WalletInitBroadcastData , TransactionBroadcastData } from './lib/iface' ;
3132
3233export interface TransactionExplanation extends BaseTransactionExplanation {
3334 type : TransactionType ;
@@ -102,6 +103,108 @@ export class Canton extends BaseCoin {
102103 return 'eddsa' ;
103104 }
104105
106+ /**
107+ * Returns the extended payload that needs to be signed for Canton EdDSA operations.
108+ * Canton requires signing a structured payload containing transaction metadata and signable hash,
109+ * not just the serialized transaction.
110+ *
111+ * @param {string } serializedTx - the unsigned transaction in broadcast format (base64)
112+ * @returns {Promise<Buffer> } - the extended payload (topology + hash, or prepared tx + hash)
113+ */
114+ async getSignablePayload ( serializedTx : string ) : Promise < Buffer > {
115+ try {
116+ // Decode the serialized transaction
117+ const decoded = JSON . parse ( Buffer . from ( serializedTx , 'base64' ) . toString ( 'utf8' ) ) as
118+ | WalletInitBroadcastData
119+ | TransactionBroadcastData ;
120+
121+ // Extract the signable payload (preparedTransactionHash in base64 format)
122+ let signableHex = '' ;
123+ if ( 'prepareCommandResponse' in decoded && decoded . prepareCommandResponse ) {
124+ signableHex = Buffer . from ( decoded . prepareCommandResponse . preparedTransactionHash , 'base64' ) . toString ( 'hex' ) ;
125+ } else {
126+ // Fallback: if unable to extract, return empty buffer
127+ signableHex = '' ;
128+ }
129+
130+ // Build extended payload based on transaction type
131+ if ( 'preparedParty' in decoded && decoded . preparedParty && decoded . preparedParty . topologyTransactions ) {
132+ // WalletInitBuilder format: [txnType] || itemCount || [lenOfTx || tx]... || signableHex
133+ return this . buildWalletInitPayload ( decoded as WalletInitBroadcastData , signableHex ) ;
134+ }
135+
136+ // TransactionBuilder format: itemCount || lenOfTx || preparedTransaction || signableHex
137+ if ( 'prepareCommandResponse' in decoded && decoded . prepareCommandResponse ?. preparedTransaction ) {
138+ return this . buildTransactionPayload ( decoded as TransactionBroadcastData , signableHex ) ;
139+ }
140+
141+ // Fallback: return signableHex only if no extended format detected
142+ return Buffer . from ( signableHex , 'hex' ) ;
143+ } catch ( e ) {
144+ // If parsing fails, fall back to base implementation
145+ return Buffer . from ( serializedTx ) ;
146+ }
147+ }
148+
149+ /**
150+ * Build WalletInitBuilder extended payload format.
151+ * Format: [txnType (optional)] || itemCount (4 bytes LE) || [lenOfTx (4 bytes LE) || tx]... || signableHex
152+ */
153+ private buildWalletInitPayload ( decoded : WalletInitBroadcastData , signableHex : string ) : Buffer {
154+ const shouldIncludeTxnType = decoded . preparedParty . shouldIncludeTxnType ?? false ;
155+ const topologyTransactions = decoded . preparedParty . topologyTransactions ;
156+ const itemCount = topologyTransactions . length + 1 ;
157+
158+ const parts : Buffer [ ] = [ ] ;
159+
160+ // Add txnType if required (version >0.5.x)
161+ if ( shouldIncludeTxnType ) {
162+ const txnTypeBuff = Buffer . alloc ( 4 ) ;
163+ txnTypeBuff . writeUInt32LE ( 0 , 0 ) ;
164+ parts . push ( txnTypeBuff ) ;
165+ }
166+
167+ // Add item count
168+ const itemCountBuff = Buffer . alloc ( 4 ) ;
169+ itemCountBuff . writeUInt32LE ( itemCount , 0 ) ;
170+ parts . push ( itemCountBuff ) ;
171+
172+ // Add topology transactions with length prefixes
173+ for ( const tx of topologyTransactions ) {
174+ const txBuffer = Buffer . from ( tx , 'base64' ) ;
175+ const lenBuff = Buffer . alloc ( 4 ) ;
176+ lenBuff . writeUInt32LE ( txBuffer . length , 0 ) ;
177+ parts . push ( lenBuff , txBuffer ) ;
178+ }
179+
180+ // Add signable hash
181+ parts . push ( Buffer . from ( signableHex , 'hex' ) ) ;
182+
183+ return Buffer . concat ( parts ) ;
184+ }
185+
186+ /**
187+ * Build TransactionBuilder extended payload format.
188+ * Format: itemCount (4 bytes LE) || lenOfTx (4 bytes LE) || preparedTransaction || signableHex
189+ */
190+ private buildTransactionPayload ( decoded : TransactionBroadcastData , signableHex : string ) : Buffer {
191+ const preparedTx = decoded . prepareCommandResponse ?. preparedTransaction ;
192+ if ( ! preparedTx ) {
193+ return Buffer . from ( signableHex , 'hex' ) ;
194+ }
195+
196+ const preparedTxBuffer = Buffer . from ( preparedTx , 'base64' ) ;
197+ const itemCount = 2 ; // prepared transaction & signable payload
198+
199+ const itemCountBuff = Buffer . alloc ( 4 ) ;
200+ itemCountBuff . writeUInt32LE ( itemCount , 0 ) ;
201+
202+ const lenBuff = Buffer . alloc ( 4 ) ;
203+ lenBuff . writeUInt32LE ( preparedTxBuffer . length , 0 ) ;
204+
205+ return Buffer . concat ( [ itemCountBuff , lenBuff , preparedTxBuffer , Buffer . from ( signableHex , 'hex' ) ] ) ;
206+ }
207+
105208 /** @inheritDoc */
106209 async verifyTransaction ( params : VerifyTransactionOptions ) : Promise < boolean > {
107210 const coinConfig = coins . get ( this . getChain ( ) ) ;
0 commit comments