@@ -31,6 +31,8 @@ public class Account {
3131 private static final String SIGN_ALGO = "EdDSA" ;
3232 private static final int PK_SIZE = 32 ;
3333 private static final int PK_X509_PREFIX_LENGTH = 12 ; // Ed25519 specific
34+ private static final int SK_PKCS_PREFIX_LENGTH = 16 ; // Ed25519 specific
35+ private static final int SK_SEPARATOR_LENGTH = 3 ; // separator is 0x81 0x21 0x00
3436 private static final int SK_SIZE = 32 ;
3537 private static final int SK_SIZE_BITS = SK_SIZE * 8 ;
3638 private static final byte [] BID_SIGN_PREFIX = ("aB" ).getBytes (StandardCharsets .UTF_8 );
@@ -73,6 +75,37 @@ private Account(SecureRandom randomSrc) throws NoSuchAlgorithmException {
7375 this .address = new Address (Arrays .copyOf (raw , raw .length ));
7476 }
7577
78+ // Derive an Account object from only keypair: {privateKey, publicKey}
79+ public Account (KeyPair pkPair ) {
80+ CryptoProvider .setupIfNeeded ();
81+ if (!pkPair .getPrivate ().getAlgorithm ().equals (KEY_ALGO )) {
82+ throw new IllegalArgumentException ("Keypair algorithm do not match with expected " + KEY_ALGO );
83+ }
84+ this .privateKeyPair = pkPair ;
85+ byte [] raw = this .getClearTextPublicKey ();
86+ this .address = new Address (Arrays .copyOf (raw , raw .length ));
87+ }
88+
89+ // Derive an Account from only private key
90+ public Account (PrivateKey pk ) throws NoSuchAlgorithmException {
91+ CryptoProvider .setupIfNeeded ();
92+ if (!pk .getAlgorithm ().equals (KEY_ALGO ))
93+ throw new IllegalArgumentException ("Account cannot be generated from a non-Ed25519 key" );
94+
95+ if (pk .getEncoded ().length != SK_PKCS_PREFIX_LENGTH + SK_SIZE + SK_SEPARATOR_LENGTH + PK_SIZE )
96+ throw new RuntimeException ("Private Key cannot generate clear private key bytes" );
97+
98+ byte [] clearPrivateKey = new byte [SK_SIZE ];
99+ System .arraycopy (pk .getEncoded (), SK_PKCS_PREFIX_LENGTH , clearPrivateKey , 0 , SK_SIZE );
100+
101+ KeyPairGenerator gen = KeyPairGenerator .getInstance (KEY_ALGO );
102+ gen .initialize (SK_SIZE_BITS , new FixedSecureRandom (clearPrivateKey ));
103+ this .privateKeyPair = gen .generateKeyPair ();
104+ // now, convert public key to an address
105+ byte [] raw = this .getClearTextPublicKey ();
106+ this .address = new Address (Arrays .copyOf (raw , raw .length ));
107+ }
108+
76109 /**
77110 * Convenience method for getting the underlying public key for raw operations.
78111 * @return the public key as length 32 byte array.
@@ -255,7 +288,7 @@ public static BigInteger estimatedEncodedSize(Transaction tx) throws NoSuchAlgor
255288 // SignTransaction does the signing, but also sets authAddr which is not desired here.
256289 long length = Encoder .encodeToMsgPack (
257290 new SignedTransaction (
258- tx ,
291+ tx ,
259292 new Account ().rawSignBytes (
260293 Arrays .copyOf (tx .bytesToSign (), tx .bytesToSign ().length )),
261294 tx .txID ())).length ;
@@ -307,10 +340,6 @@ public Signature signBytes(byte[] bytes) throws NoSuchAlgorithmException {
307340 * @throws NoSuchAlgorithmException if could not sign tx
308341 */
309342 public SignedTransaction signMultisigTransaction (MultisigAddress from , Transaction tx ) throws NoSuchAlgorithmException {
310- // check that from addr of tx matches multisig preimage
311- if (!tx .sender .toString ().equals (from .toString ())) {
312- throw new IllegalArgumentException ("Transaction sender does not match multisig account" );
313- }
314343 // check that account secret key is in multisig pk list
315344 Ed25519PublicKey myPK = this .getEd25519PublicKey ();
316345 int myI = from .publicKeys .indexOf (myPK );
@@ -327,7 +356,13 @@ public SignedTransaction signMultisigTransaction(MultisigAddress from, Transacti
327356 mSig .subsigs .add (new MultisigSubsig (from .publicKeys .get (i )));
328357 }
329358 }
330- return new SignedTransaction (tx , mSig , txSig .transactionID );
359+ // generate signed transaction
360+ SignedTransaction stx = new SignedTransaction (tx , mSig , txSig .transactionID );
361+ // if the transaction sender address is not multi-sig address
362+ // set the auth address as the multi-sig address
363+ if (!tx .sender .equals (from .toAddress ()))
364+ stx .authAddr = from .toAddress ();
365+ return stx ;
331366 }
332367
333368 /**
@@ -340,13 +375,19 @@ public static SignedTransaction mergeMultisigTransactions(SignedTransaction... t
340375 throw new IllegalArgumentException ("cannot merge a single transaction" );
341376 }
342377 SignedTransaction merged = txs [0 ];
343- for (int i = 0 ; i < txs . length ; i ++ ) {
378+ for (SignedTransaction tx : txs ) {
344379 // check that multisig parameters match
345- SignedTransaction tx = txs [i ];
346380 if (tx .mSig .version != merged .mSig .version ||
347381 tx .mSig .threshold != merged .mSig .threshold ) {
348382 throw new IllegalArgumentException ("transaction msig parameters do not match" );
349383 }
384+ // check multi-sig address match
385+ if (!tx .mSig .convertToMultisigAddress ().equals (merged .mSig .convertToMultisigAddress ()))
386+ throw new IllegalArgumentException ("transaction msig addresses do not match" );
387+ // check that authAddr match
388+ if (!tx .authAddr .equals (merged .authAddr )) {
389+ throw new IllegalArgumentException ("transaction msig auth addr do not match" );
390+ }
350391 for (int j = 0 ; j < tx .mSig .subsigs .size (); j ++) {
351392 MultisigSubsig myMsig = merged .mSig .subsigs .get (j );
352393 MultisigSubsig theirMsig = tx .mSig .subsigs .get (j );
@@ -514,6 +555,25 @@ public LogicsigSignature appendToLogicsig(LogicsigSignature lsig) throws Illegal
514555
515556 }
516557
558+ public static SignedTransaction signLogicTransactionWithAddress (LogicsigSignature lsig , Address lsigAddr , Transaction tx )
559+ throws IllegalArgumentException , IOException {
560+ try {
561+ if (!lsig .verify (lsigAddr ))
562+ throw new IllegalArgumentException ("verification failed on logic sig" );
563+ } catch (NoSuchAlgorithmException ex ) {
564+ throw new IllegalArgumentException ("verification failed on logic sig" , ex );
565+ }
566+
567+ try {
568+ SignedTransaction stx = new SignedTransaction (tx , lsig , tx .txID ());
569+ if (!stx .tx .sender .equals (lsigAddr ))
570+ stx .authAddr = lsigAddr ;
571+ return stx ;
572+ } catch (Exception ex ) {
573+ throw new IOException ("could not encode transactions" , ex );
574+ }
575+ }
576+
517577 /**
518578 * Creates SignedTransaction from LogicsigSignature and Transaction.
519579 * LogicsigSignature must be valid and verifiable against transaction sender field.
@@ -522,14 +582,20 @@ public LogicsigSignature appendToLogicsig(LogicsigSignature lsig) throws Illegal
522582 * @return SignedTransaction
523583 */
524584 public static SignedTransaction signLogicsigTransaction (LogicsigSignature lsig , Transaction tx ) throws IllegalArgumentException , IOException {
525- if (!lsig .verify (tx .sender )) {
526- throw new IllegalArgumentException ("verification failed" );
527- }
528-
585+ boolean hasSig = lsig .sig != null ;
586+ boolean hasMsig = lsig .msig != null ;
587+ Address lsigAddr ;
529588 try {
530- return new SignedTransaction (tx , lsig , tx .txID ());
589+ if (hasSig ) {
590+ lsigAddr = tx .sender ;
591+ } else if (hasMsig ) {
592+ lsigAddr = lsig .msig .convertToMultisigAddress ().toAddress ();
593+ } else {
594+ lsigAddr = lsig .toAddress ();
595+ }
596+ return Account .signLogicTransactionWithAddress (lsig , lsigAddr , tx );
531597 } catch (Exception ex ) {
532- throw new IOException ("could not encode transactions " , ex );
598+ throw new IOException ("could not sign transaction " , ex );
533599 }
534600 }
535601
@@ -569,8 +635,25 @@ public byte[] toSeed() throws GeneralSecurityException {
569635 return Mnemonic .toKey (mnemonic );
570636 }
571637
638+ @ Override
639+ public boolean equals (Object obj ) {
640+ if (!(obj instanceof Account ))
641+ return false ;
642+ Account oAccount = (Account ) obj ;
643+ boolean addressMatch = Arrays .equals (this .address .getBytes (), oAccount .address .getBytes ());
644+ boolean privateKeyMatch = Arrays .equals (
645+ this .privateKeyPair .getPrivate ().getEncoded (),
646+ oAccount .privateKeyPair .getPrivate ().getEncoded ()
647+ );
648+ boolean publicKeyMatch = Arrays .equals (
649+ this .privateKeyPair .getPublic ().getEncoded (),
650+ oAccount .privateKeyPair .getPublic ().getEncoded ()
651+ );
652+ return addressMatch && privateKeyMatch && publicKeyMatch ;
653+ }
654+
572655 // Return a pre-set seed in response to nextBytes or generateSeed
573- private static class FixedSecureRandom extends SecureRandom {
656+ public static class FixedSecureRandom extends SecureRandom {
574657 private final byte [] fixedValue ;
575658 private int index = 0 ;
576659
0 commit comments