@@ -17,12 +17,15 @@ import {
17
17
SignedTokenTransferOptions ,
18
18
StacksTestnet ,
19
19
standardPrincipalCV ,
20
+ TransactionSigner ,
20
21
UnsignedTokenTransferOptions ,
21
22
} from '@blockstack/stacks-transactions' ;
22
23
import * as BN from 'bn.js' ;
23
24
import { getCoreNodeEndpoint , StacksCoreRpcClient } from '../core-rpc/client' ;
24
25
import { bufferToHexPrefixString } from '../helpers' ;
25
26
import {
27
+ RosettaConstructionCombineRequest ,
28
+ RosettaConstructionCombineResponse ,
26
29
RosettaConstructionDeriveRequest ,
27
30
RosettaConstructionDeriveResponse ,
28
31
RosettaConstructionHashRequest ,
@@ -36,6 +39,8 @@ import {
36
39
} from '@blockstack/stacks-blockchain-api-types' ;
37
40
import { RosettaConstants , RosettaErrors } from '../api/rosetta-constants' ;
38
41
import { GetStacksTestnetNetwork , testnetKeys } from '../api/routes/debug' ;
42
+ import { getSignature } from '../rosetta-helpers' ;
43
+ import { cloneDeep } from '@blockstack/stacks-transactions/lib/utils' ;
39
44
40
45
describe ( 'Rosetta API' , ( ) => {
41
46
let db : PgDataStore ;
@@ -123,6 +128,13 @@ describe('Rosetta API', () => {
123
128
{ code : 630 , message : 'Amount not available' , retriable : false } ,
124
129
{ code : 631 , message : 'Fees not available' , retriable : false } ,
125
130
{ code : 632 , message : 'Public key not available' , retriable : false } ,
131
+ { code : 633 , message : 'no signature found' , retriable : false } ,
132
+ { code : 634 , message : 'Invalid Signature' , retriable : false } ,
133
+ {
134
+ code : 635 ,
135
+ message : 'Signature(s) not verified with this public key(s)' ,
136
+ retriable : false ,
137
+ } ,
126
138
] ,
127
139
historical_balance_lookup : true ,
128
140
} ,
@@ -1400,6 +1412,233 @@ describe('Rosetta API', () => {
1400
1412
expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1401
1413
} ) ;
1402
1414
1415
+ test ( 'combine success' , async ( ) => {
1416
+ const publicKey = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1417
+
1418
+ const txOptions : UnsignedTokenTransferOptions = {
1419
+ publicKey : publicKey ,
1420
+ recipient : standardPrincipalCV ( testnetKeys [ 1 ] . stacksAddress ) ,
1421
+ amount : new BigNum ( 12345 ) ,
1422
+ network : GetStacksTestnetNetwork ( ) ,
1423
+ memo : 'test memo' ,
1424
+ nonce : new BigNum ( 0 ) ,
1425
+ fee : new BigNum ( 200 ) ,
1426
+ } ;
1427
+
1428
+ const unsignedTransaction = await makeUnsignedSTXTokenTransfer ( txOptions ) ;
1429
+ const unsignedSerializedTx = unsignedTransaction . serialize ( ) . toString ( 'hex' ) ;
1430
+
1431
+ const signer = new TransactionSigner ( unsignedTransaction ) ;
1432
+ signer . signOrigin ( createStacksPrivateKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1433
+ const signedSerializedTx = unsignedTransaction . serialize ( ) . toString ( 'hex' ) ;
1434
+
1435
+ const signature = getSignature ( unsignedTransaction ) ;
1436
+ if ( ! signature ) return ;
1437
+
1438
+ const request : RosettaConstructionCombineRequest = {
1439
+ network_identifier : {
1440
+ blockchain : 'stacks' ,
1441
+ network : 'testnet' ,
1442
+ } ,
1443
+ unsigned_transaction : unsignedSerializedTx ,
1444
+ signatures : [
1445
+ {
1446
+ signing_payload : {
1447
+ hex_bytes : signature . data ,
1448
+ signature_type : 'ecdsa' ,
1449
+ } ,
1450
+ public_key : {
1451
+ hex_bytes : publicKey ,
1452
+ curve_type : 'secp256k1' ,
1453
+ } ,
1454
+ signature_type : 'ecdsa' ,
1455
+ hex_bytes : signature . data ,
1456
+ } ,
1457
+ ] ,
1458
+ } ;
1459
+
1460
+ const result = await supertest ( api . server )
1461
+ . post ( `/rosetta/v1/construction/combine` )
1462
+ . send ( request ) ;
1463
+
1464
+ expect ( result . status ) . toBe ( 200 ) ;
1465
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1466
+
1467
+ const expectedResponse : RosettaConstructionCombineResponse = {
1468
+ signed_transaction : signedSerializedTx ,
1469
+ } ;
1470
+
1471
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1472
+ } ) ;
1473
+
1474
+ test ( 'combine invalid transaction' , async ( ) => {
1475
+ const request : RosettaConstructionCombineRequest = {
1476
+ network_identifier : {
1477
+ blockchain : 'stacks' ,
1478
+ network : 'testnet' ,
1479
+ } ,
1480
+ unsigned_transaction : 'invalid transaction' ,
1481
+ signatures : [
1482
+ {
1483
+ signing_payload : {
1484
+ hex_bytes :
1485
+ '0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f' ,
1486
+ signature_type : 'ecdsa' ,
1487
+ } ,
1488
+ public_key : {
1489
+ hex_bytes : '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51' ,
1490
+ curve_type : 'secp256k1' ,
1491
+ } ,
1492
+ signature_type : 'ecdsa' ,
1493
+ hex_bytes :
1494
+ '0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f' ,
1495
+ } ,
1496
+ ] ,
1497
+ } ;
1498
+
1499
+ const result = await supertest ( api . server )
1500
+ . post ( `/rosetta/v1/construction/combine` )
1501
+ . send ( request ) ;
1502
+
1503
+ expect ( result . status ) . toBe ( 400 ) ;
1504
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1505
+
1506
+ const expectedResponse = RosettaErrors . invalidTransactionString ;
1507
+
1508
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1509
+ } ) ;
1510
+
1511
+ test ( 'combine invalid signature' , async ( ) => {
1512
+ const request : RosettaConstructionCombineRequest = {
1513
+ network_identifier : {
1514
+ blockchain : 'stacks' ,
1515
+ network : 'testnet' ,
1516
+ } ,
1517
+ unsigned_transaction :
1518
+ '00000000010400539886f96611ba3ba6cef9618f8c78118b37c5be0000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000' ,
1519
+ signatures : [
1520
+ {
1521
+ signing_payload : {
1522
+ hex_bytes : 'invalid signature' ,
1523
+ signature_type : 'ecdsa' ,
1524
+ } ,
1525
+ public_key : {
1526
+ hex_bytes : '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51' ,
1527
+ curve_type : 'secp256k1' ,
1528
+ } ,
1529
+ signature_type : 'ecdsa' ,
1530
+ hex_bytes :
1531
+ '0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f' ,
1532
+ } ,
1533
+ ] ,
1534
+ } ;
1535
+
1536
+ const result = await supertest ( api . server )
1537
+ . post ( `/rosetta/v1/construction/combine` )
1538
+ . send ( request ) ;
1539
+
1540
+ expect ( result . status ) . toBe ( 400 ) ;
1541
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1542
+
1543
+ const expectedResponse = RosettaErrors . invalidSignature ;
1544
+
1545
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1546
+ } ) ;
1547
+
1548
+ test ( 'combine signature not verified' , async ( ) => {
1549
+ const publicKey = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1550
+
1551
+ const txOptions : UnsignedTokenTransferOptions = {
1552
+ publicKey : publicKey ,
1553
+ recipient : standardPrincipalCV ( testnetKeys [ 1 ] . stacksAddress ) ,
1554
+ amount : new BigNum ( 12345 ) ,
1555
+ network : GetStacksTestnetNetwork ( ) ,
1556
+ memo : 'test memo' ,
1557
+ nonce : new BigNum ( 0 ) ,
1558
+ fee : new BigNum ( 200 ) ,
1559
+ } ;
1560
+
1561
+ const unsignedTransaction = await makeUnsignedSTXTokenTransfer ( txOptions ) ;
1562
+ const unsignedSerializedTx = unsignedTransaction . serialize ( ) . toString ( 'hex' ) ;
1563
+
1564
+ const signer = new TransactionSigner ( unsignedTransaction ) ;
1565
+ signer . signOrigin ( createStacksPrivateKey ( testnetKeys [ 1 ] . secretKey ) ) ; // use different secret key to sign
1566
+
1567
+ const signature = getSignature ( unsignedTransaction ) ;
1568
+ if ( ! signature ) return ;
1569
+
1570
+ const request : RosettaConstructionCombineRequest = {
1571
+ network_identifier : {
1572
+ blockchain : 'stacks' ,
1573
+ network : 'testnet' ,
1574
+ } ,
1575
+ unsigned_transaction : unsignedSerializedTx ,
1576
+ signatures : [
1577
+ {
1578
+ signing_payload : {
1579
+ hex_bytes : signature . data ,
1580
+ signature_type : 'ecdsa' ,
1581
+ } ,
1582
+ public_key : {
1583
+ hex_bytes : '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51' ,
1584
+ curve_type : 'secp256k1' ,
1585
+ } ,
1586
+ signature_type : 'ecdsa' ,
1587
+ hex_bytes : signature . data ,
1588
+ } ,
1589
+ ] ,
1590
+ } ;
1591
+
1592
+ const result = await supertest ( api . server )
1593
+ . post ( `/rosetta/v1/construction/combine` )
1594
+ . send ( request ) ;
1595
+
1596
+ expect ( result . status ) . toBe ( 400 ) ;
1597
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1598
+
1599
+ const expectedResponse = RosettaErrors . signatureNotVerified ;
1600
+
1601
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1602
+ } ) ;
1603
+
1604
+ test ( 'combine invalid public key' , async ( ) => {
1605
+ const request : RosettaConstructionCombineRequest = {
1606
+ network_identifier : {
1607
+ blockchain : 'stacks' ,
1608
+ network : 'testnet' ,
1609
+ } ,
1610
+ unsigned_transaction :
1611
+ '80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b4000136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f03020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000' ,
1612
+ signatures : [
1613
+ {
1614
+ signing_payload : {
1615
+ hex_bytes :
1616
+ '0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f' ,
1617
+ signature_type : 'ecdsa' ,
1618
+ } ,
1619
+ public_key : {
1620
+ hex_bytes : 'invalid public key' ,
1621
+ curve_type : 'secp256k1' ,
1622
+ } ,
1623
+ signature_type : 'ecdsa' ,
1624
+ hex_bytes :
1625
+ '0136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f' ,
1626
+ } ,
1627
+ ] ,
1628
+ } ;
1629
+
1630
+ const result = await supertest ( api . server )
1631
+ . post ( `/rosetta/v1/construction/combine` )
1632
+ . send ( request ) ;
1633
+
1634
+ expect ( result . status ) . toBe ( 400 ) ;
1635
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1636
+
1637
+ const expectedResponse = RosettaErrors . signatureNotVerified ;
1638
+
1639
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1640
+ } ) ;
1641
+
1403
1642
/* rosetta construction end */
1404
1643
1405
1644
afterAll ( async ( ) => {
0 commit comments