@@ -233,6 +233,52 @@ describe('ALGO:', function () {
233233 explain . outputs [ 0 ] . amount . should . equal ( amount . toString ( ) ) ;
234234 explain . outputs [ 0 ] . address . should . equal ( destination ) ;
235235 } ) ;
236+
237+ it ( 'should surface the rekey target when explaining a transfer transaction that rekeys the account' , async function ( ) {
238+ const sender = AlgoResources . accounts . account1 . address ;
239+ const destination = AlgoResources . accounts . account2 . address ;
240+ const rekeyAddress = AlgoResources . accounts . account3 . address ;
241+ const txBuilder = buildBaseTransferTransaction ( { sender, destination, amount : 10000 } ) ;
242+ txBuilder . reKeyTo ( { address : rekeyAddress } ) ;
243+ const unsignedTransaction = await txBuilder . build ( ) ;
244+ const unsignedHex = Buffer . from ( unsignedTransaction . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
245+ const explain = await basecoin . explainTransaction ( {
246+ txHex : unsignedHex ,
247+ feeInfo : { fee : '1000' } ,
248+ } ) ;
249+ explain . reKeyTo . should . equal ( rekeyAddress ) ;
250+ explain . displayOrder . should . containEql ( 'reKeyTo' ) ;
251+ explain . outputs [ 0 ] . address . should . equal ( destination ) ;
252+ } ) ;
253+
254+ it ( 'should explain the halfSigned transaction when both txHex and halfSigned txHex are provided' , async function ( ) {
255+ const sender = AlgoResources . accounts . account1 . address ;
256+ const destination = AlgoResources . accounts . account2 . address ;
257+ const rekeyAddress = AlgoResources . accounts . account3 . address ;
258+
259+ // plain unsigned payment without a rekey, used as the misleading txHex
260+ const plainHex = Buffer . from (
261+ ( await buildBaseTransferTransaction ( { sender, destination, amount : 10000 } ) . build ( ) ) . toBroadcastFormat ( )
262+ ) . toString ( 'hex' ) ;
263+
264+ // half-signed payment that rekeys the account, used as the halfSigned txHex
265+ const rekeyedBuilder = buildBaseTransferTransaction ( { sender, destination, amount : 10000 } ) ;
266+ rekeyedBuilder . reKeyTo ( { address : rekeyAddress } ) ;
267+ rekeyedBuilder . numberOfSigners ( 2 ) ;
268+ rekeyedBuilder . setSigners ( [ AlgoResources . accounts . account1 . address , AlgoResources . accounts . account3 . address ] ) ;
269+ rekeyedBuilder . sign ( { key : AlgoResources . accounts . account1 . prvKey } ) ;
270+ const halfSignedHex = Buffer . from ( ( await rekeyedBuilder . build ( ) ) . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
271+
272+ // signTransaction signs the halfSigned hex when both are present, so the
273+ // explanation must come from the same bytes, not from txHex
274+ const explain = await basecoin . explainTransaction ( {
275+ txHex : plainHex ,
276+ halfSigned : { txHex : halfSignedHex } ,
277+ feeInfo : { fee : '1000' } ,
278+ } ) ;
279+ explain . reKeyTo . should . equal ( rekeyAddress ) ;
280+ explain . displayOrder . should . containEql ( 'reKeyTo' ) ;
281+ } ) ;
236282 } ) ;
237283
238284 describe ( 'Asset Transfer Builder: ' , ( ) => {
@@ -349,6 +395,23 @@ describe('ALGO:', function () {
349395 explain . outputs [ 0 ] . address . should . equal ( destination ) ;
350396 explain . tokenId . should . equal ( 1 ) ;
351397 } ) ;
398+
399+ it ( 'should surface the rekey target when explaining an asset transfer transaction that rekeys the account' , async function ( ) {
400+ const sender = AlgoResources . accounts . account1 . address ;
401+ const destination = AlgoResources . accounts . account2 . address ;
402+ const rekeyAddress = AlgoResources . accounts . account3 . address ;
403+ const txBuilder = buildBaseAssetTransferTransaction ( { sender, destination, amount : 10000 , tokenId : 1 } ) ;
404+ txBuilder . reKeyTo ( { address : rekeyAddress } ) ;
405+ const unsignedTransaction = await txBuilder . build ( ) ;
406+ const unsignedHex = Buffer . from ( unsignedTransaction . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
407+ const explain = await basecoin . explainTransaction ( {
408+ txHex : unsignedHex ,
409+ feeInfo : { fee : '1000' } ,
410+ } ) ;
411+ explain . reKeyTo . should . equal ( rekeyAddress ) ;
412+ explain . displayOrder . should . containEql ( 'reKeyTo' ) ;
413+ explain . outputs [ 0 ] . address . should . equal ( destination ) ;
414+ } ) ;
352415 } ) ;
353416
354417 describe ( 'Wallet Init Builder: ' , ( ) => {
@@ -538,6 +601,21 @@ describe('ALGO:', function () {
538601 explain . voteLast . should . equal ( 100 ) ;
539602 explain . voteKeyDilution . should . equal ( 9 ) ;
540603 } ) ;
604+
605+ it ( 'should surface the rekey target when explaining a keyreg transaction that rekeys the account' , async function ( ) {
606+ const sender = AlgoResources . accounts . account1 ;
607+ const rekeyAddress = AlgoResources . accounts . account3 . address ;
608+ const txBuilder = buildBaseKeyRegTransaction ( { sender } ) ;
609+ txBuilder . reKeyTo ( { address : rekeyAddress } ) ;
610+ const unsignedTransaction = await txBuilder . build ( ) ;
611+ const unsignedHex = Buffer . from ( unsignedTransaction . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
612+ const explain = await basecoin . explainTransaction ( {
613+ txHex : unsignedHex ,
614+ feeInfo : { fee : '1000' } ,
615+ } ) ;
616+ explain . reKeyTo . should . equal ( rekeyAddress ) ;
617+ explain . displayOrder . should . containEql ( 'reKeyTo' ) ;
618+ } ) ;
541619 } ) ;
542620 describe ( 'Sign transaction' , ( ) => {
543621 it ( 'should sign transaction' , async function ( ) {
@@ -648,6 +726,99 @@ describe('ALGO:', function () {
648726 } ) ;
649727 signed . txHex . should . equal ( AlgoResources . rawTx . transfer . multisig ) ;
650728 } ) ;
729+
730+ it ( 'should refuse to sign a transaction that rekeys the account' , async function ( ) {
731+ const factory = new TransactionBuilderFactory ( coins . get ( 'algo' ) ) ;
732+ const txBuilder = factory
733+ . getTransferBuilder ( )
734+ . sender ( { address : AlgoResources . accounts . account1 . address } )
735+ . to ( { address : AlgoResources . accounts . account2 . address } )
736+ . amount ( 10000 )
737+ . isFlatFee ( true )
738+ . fee ( { fee : '1000' } )
739+ . firstRound ( 1 )
740+ . lastRound ( 100 )
741+ . testnet ( ) ;
742+ txBuilder . reKeyTo ( { address : AlgoResources . accounts . account3 . address } ) ;
743+ const unsignedTx = await txBuilder . build ( ) ;
744+ const unsignedHex = Buffer . from ( unsignedTx . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
745+
746+ await basecoin
747+ . signTransaction ( {
748+ txPrebuild : {
749+ txHex : unsignedHex ,
750+ keys : [ AlgoResources . accounts . account1 . pubKey . toString ( 'hex' ) ] ,
751+ addressVersion : 1 ,
752+ } ,
753+ prv : AlgoResources . accounts . account1 . prvKey ,
754+ } )
755+ . should . be . rejectedWith (
756+ new RegExp (
757+ `transaction rekeys the account to ${ AlgoResources . accounts . account3 . address } : refusing to sign unless the rekey is confirmed intentional`
758+ )
759+ ) ;
760+ } ) ;
761+
762+ it ( 'should sign a transaction that rekeys the account when the rekey is explicitly allowed' , async function ( ) {
763+ const factory = new TransactionBuilderFactory ( coins . get ( 'algo' ) ) ;
764+ const txBuilder = factory
765+ . getTransferBuilder ( )
766+ . sender ( { address : AlgoResources . accounts . account1 . address } )
767+ . to ( { address : AlgoResources . accounts . account2 . address } )
768+ . amount ( 10000 )
769+ . isFlatFee ( true )
770+ . fee ( { fee : '1000' } )
771+ . firstRound ( 1 )
772+ . lastRound ( 100 )
773+ . testnet ( ) ;
774+ txBuilder . reKeyTo ( { address : AlgoResources . accounts . account3 . address } ) ;
775+ const unsignedTx = await txBuilder . build ( ) ;
776+ const unsignedHex = Buffer . from ( unsignedTx . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
777+
778+ const signed = await basecoin . signTransaction ( {
779+ txPrebuild : {
780+ txHex : unsignedHex ,
781+ keys : [ AlgoResources . accounts . account1 . pubKey . toString ( 'hex' ) ] ,
782+ addressVersion : 1 ,
783+ } ,
784+ prv : AlgoResources . accounts . account1 . prvKey ,
785+ allowReKeyTo : AlgoResources . accounts . account3 . address ,
786+ } ) ;
787+ assert . strictEqual ( typeof signed . txHex , 'string' ) ;
788+ } ) ;
789+
790+ it ( 'should refuse to sign a rekeyed transaction when the approved rekey address does not match' , async function ( ) {
791+ const factory = new TransactionBuilderFactory ( coins . get ( 'algo' ) ) ;
792+ const txBuilder = factory
793+ . getTransferBuilder ( )
794+ . sender ( { address : AlgoResources . accounts . account1 . address } )
795+ . to ( { address : AlgoResources . accounts . account2 . address } )
796+ . amount ( 10000 )
797+ . isFlatFee ( true )
798+ . fee ( { fee : '1000' } )
799+ . firstRound ( 1 )
800+ . lastRound ( 100 )
801+ . testnet ( ) ;
802+ txBuilder . reKeyTo ( { address : AlgoResources . accounts . account3 . address } ) ;
803+ const unsignedTx = await txBuilder . build ( ) ;
804+ const unsignedHex = Buffer . from ( unsignedTx . toBroadcastFormat ( ) ) . toString ( 'hex' ) ;
805+
806+ await basecoin
807+ . signTransaction ( {
808+ txPrebuild : {
809+ txHex : unsignedHex ,
810+ keys : [ AlgoResources . accounts . account1 . pubKey . toString ( 'hex' ) ] ,
811+ addressVersion : 1 ,
812+ } ,
813+ prv : AlgoResources . accounts . account1 . prvKey ,
814+ allowReKeyTo : AlgoResources . accounts . account2 . address ,
815+ } )
816+ . should . be . rejectedWith (
817+ new RegExp (
818+ `transaction rekeys the account to ${ AlgoResources . accounts . account3 . address } : refusing to sign unless the rekey is confirmed intentional`
819+ )
820+ ) ;
821+ } ) ;
651822 } ) ;
652823
653824 describe ( 'Sign message' , ( ) => {
0 commit comments