1- import {
2- BaseKey ,
3- BaseTransaction ,
4- InvalidTransactionError ,
5- ParseTransactionError ,
6- TransactionType ,
7- } from '@bitgo/sdk-core' ;
1+ import { BaseKey , BaseTransaction , InvalidTransactionError , TransactionType } from '@bitgo/sdk-core' ;
82import { BaseCoin as CoinConfig } from '@bitgo/statics' ;
93import { localForger } from '@taquito/local-forging' ;
104import { OpKind } from '@taquito/rpc' ;
@@ -20,6 +14,32 @@ import {
2014} from './multisigUtils' ;
2115import * as Utils from './utils' ;
2216
17+ const SIGNATURE_HEX_LENGTH = 128 ;
18+
19+ async function tryParseSigned (
20+ serialized : string
21+ ) : Promise < { parsed : ParsedTransaction ; transactionId : string } | undefined > {
22+ if ( serialized . length <= SIGNATURE_HEX_LENGTH ) {
23+ return undefined ;
24+ }
25+ // If `serialized` really is `forge(ops) || signature`, stripping the trailing 64
26+ // bytes gives the exact forge bytes and forge(parse(...)) reproduces them. If
27+ // `serialized` was unsigned, stripping cuts into the operation contents: parse
28+ // either throws or recovers a truncated parse whose forge does not match. So a
29+ // clean round-trip is what proves the trailing bytes were a signature.
30+ const operationBytes = serialized . slice ( 0 , - SIGNATURE_HEX_LENGTH ) ;
31+ try {
32+ const parsed = await localForger . parse ( operationBytes ) ;
33+ const roundTrip = await localForger . forge ( parsed ) ;
34+ if ( roundTrip !== operationBytes ) {
35+ return undefined ;
36+ }
37+ return { parsed, transactionId : await Utils . calculateTransactionId ( serialized ) } ;
38+ } catch ( _ ) {
39+ return undefined ;
40+ }
41+ }
42+
2343/**
2444 * Tezos transaction model.
2545 */
@@ -49,22 +69,16 @@ export class Transaction extends BaseTransaction {
4969 */
5070 async initFromSerializedTransaction ( serializedTransaction : string ) : Promise < void > {
5171 this . _encodedTransaction = serializedTransaction ;
52- try {
53- const parsedTransaction = await localForger . parse ( serializedTransaction ) ;
54- await this . initFromParsedTransaction ( parsedTransaction ) ;
55- } catch ( e ) {
56- // If it throws, it is possible the serialized transaction is signed, which is not supported
57- // by local-forging. Try extracting the last 64 bytes and parse it again.
58- const unsignedSerializedTransaction = serializedTransaction . slice ( 0 , - 128 ) ;
59- const signature = serializedTransaction . slice ( - 128 ) ;
60- if ( Utils . isValidSignature ( signature ) ) {
61- throw new ParseTransactionError ( 'Invalid transaction' ) ;
62- }
72+ // Only signed inputs have a transaction id (it is the hash of the signed bytes);
73+ // unsigned inputs leave it unset and let the caller populate it after signing.
74+ const signed = await tryParseSigned ( serializedTransaction ) ;
75+ if ( signed ) {
6376 // TODO: encode the signature and save it in _signature
64- const parsedTransaction = await localForger . parse ( unsignedSerializedTransaction ) ;
65- const transactionId = await Utils . calculateTransactionId ( serializedTransaction ) ;
66- await this . initFromParsedTransaction ( parsedTransaction , transactionId ) ;
77+ await this . initFromParsedTransaction ( signed . parsed , signed . transactionId ) ;
78+ return ;
6779 }
80+ const parsed = await localForger . parse ( serializedTransaction ) ;
81+ await this . initFromParsedTransaction ( parsed ) ;
6882 }
6983
7084 /**
0 commit comments