@@ -15,21 +15,35 @@ import {
15
15
RosettaConstructionPreprocessRequest ,
16
16
RosettaConstructionMetadataRequest ,
17
17
RosettaConstructionPayloadResponse ,
18
+ RosettaConstructionCombineRequest ,
19
+ RosettaConstructionCombineResponse ,
18
20
} from '@blockstack/stacks-blockchain-api-types' ;
19
21
import {
22
+ createMessageSignature ,
23
+ createTransactionAuthField ,
20
24
emptyMessageSignature ,
21
25
isSingleSig ,
26
+ MessageSignature ,
22
27
} from '@blockstack/stacks-transactions/lib/authorization' ;
23
28
import { BufferReader } from '@blockstack/stacks-transactions/lib/bufferReader' ;
24
- import { deserializeTransaction } from '@blockstack/stacks-transactions/lib/transaction' ;
29
+ import {
30
+ deserializeTransaction ,
31
+ StacksTransaction ,
32
+ } from '@blockstack/stacks-transactions/lib/transaction' ;
25
33
import {
26
34
UnsignedTokenTransferOptions ,
27
35
makeUnsignedSTXTokenTransfer ,
28
36
} from '@blockstack/stacks-transactions' ;
29
37
import * as express from 'express' ;
30
38
import { StacksCoreRpcClient } from '../../../core-rpc/client' ;
31
39
import { DataStore , DbBlock } from '../../../datastore/common' ;
32
- import { FoundOrNot , hexToBuffer , isValidC32Address , digestSha512_256 } from '../../../helpers' ;
40
+ import {
41
+ FoundOrNot ,
42
+ hexToBuffer ,
43
+ isValidC32Address ,
44
+ digestSha512_256 ,
45
+ has0xPrefix ,
46
+ } from '../../../helpers' ;
33
47
import { RosettaConstants , RosettaErrors } from '../../rosetta-constants' ;
34
48
import {
35
49
bitcoinAddressToSTXAddress ,
@@ -43,6 +57,8 @@ import {
43
57
rawTxToBaseTx ,
44
58
rawTxToStacksTransaction ,
45
59
GetStacksTestnetNetwork ,
60
+ makePresignHash ,
61
+ verifySignature ,
46
62
} from './../../../rosetta-helpers' ;
47
63
import { makeRosettaError , rosettaValidateRequest , ValidSchema } from './../../rosetta-validate' ;
48
64
@@ -405,7 +421,77 @@ export function createRosettaConstructionRouter(db: DataStore): RouterWithAsync
405
421
} ) ;
406
422
407
423
//construction/combine endpoint
408
- router . postAsync ( 'combine' , async ( req , res ) => { } ) ;
424
+ router . postAsync ( '/combine' , async ( req , res ) => {
425
+ const valid : ValidSchema = await rosettaValidateRequest ( req . originalUrl , req . body ) ;
426
+ if ( ! valid . valid ) {
427
+ res . status ( 400 ) . json ( makeRosettaError ( valid ) ) ;
428
+ return ;
429
+ }
430
+
431
+ const combineRequest : RosettaConstructionCombineRequest = req . body ;
432
+ const signatures = combineRequest . signatures ;
433
+
434
+ if ( has0xPrefix ( combineRequest . unsigned_transaction ) ) {
435
+ res . status ( 400 ) . json ( RosettaErrors . invalidTransactionString ) ;
436
+ return ;
437
+ }
438
+
439
+ if ( signatures . length === 0 ) {
440
+ res . status ( 400 ) . json ( RosettaErrors . noSignatures ) ;
441
+ return ;
442
+ }
443
+
444
+ let unsigned_transaction_buffer : Buffer ;
445
+ let transaction : StacksTransaction ;
446
+
447
+ try {
448
+ unsigned_transaction_buffer = hexToBuffer ( '0x' + combineRequest . unsigned_transaction ) ;
449
+ transaction = deserializeTransaction ( BufferReader . fromBuffer ( unsigned_transaction_buffer ) ) ;
450
+ } catch ( e ) {
451
+ res . status ( 400 ) . json ( RosettaErrors . invalidTransactionString ) ;
452
+ return ;
453
+ }
454
+
455
+ for ( const signature of signatures ) {
456
+ if ( signature . public_key . curve_type !== 'secp256k1' ) {
457
+ res . status ( 400 ) . json ( RosettaErrors . invalidCurveType ) ;
458
+ return ;
459
+ }
460
+ const preSignHash = makePresignHash ( transaction ) ;
461
+ if ( ! preSignHash ) {
462
+ res . status ( 400 ) . json ( RosettaErrors . invalidTransactionString ) ;
463
+ return ;
464
+ }
465
+
466
+ let newSignature : MessageSignature ;
467
+
468
+ try {
469
+ newSignature = createMessageSignature ( signature . signing_payload . hex_bytes ) ;
470
+ } catch ( error ) {
471
+ res . status ( 400 ) . json ( RosettaErrors . invalidSignature ) ;
472
+ return ;
473
+ }
474
+
475
+ if ( ! verifySignature ( preSignHash , signature . public_key . hex_bytes , newSignature ) ) {
476
+ res . status ( 400 ) . json ( RosettaErrors . signatureNotVerified ) ;
477
+ }
478
+
479
+ if ( transaction . auth . spendingCondition && isSingleSig ( transaction . auth . spendingCondition ) ) {
480
+ transaction . auth . spendingCondition . signature = newSignature ;
481
+ } else {
482
+ const authField = createTransactionAuthField ( newSignature ) ;
483
+ transaction . auth . spendingCondition ?. fields . push ( authField ) ;
484
+ }
485
+ }
486
+
487
+ const serializedTx = transaction . serialize ( ) . toString ( 'hex' ) ;
488
+
489
+ const combineResponse : RosettaConstructionCombineResponse = {
490
+ signed_transaction : serializedTx ,
491
+ } ;
492
+
493
+ res . status ( 200 ) . json ( combineResponse ) ;
494
+ } ) ;
409
495
410
496
return router ;
411
497
}
0 commit comments