Skip to content

Commit cda8a83

Browse files
Merge branch 'release/1.10.0'
2 parents 1baf04f + c8f60e9 commit cda8a83

File tree

13 files changed

+893
-71
lines changed

13 files changed

+893
-71
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.10.0
2+
- Feature/sign rekey lsig msig (#250)
3+
- Support parsing msgpack with numeric key (#262)
4+
15
# 1.9.0
26
- Support AVM 1.0
37
- Update TEAL langspec to v5

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Maven:
2323
<dependency>
2424
<groupId>com.algorand</groupId>
2525
<artifactId>algosdk</artifactId>
26-
<version>1.9.0</version>
26+
<version>1.10.0</version>
2727
</dependency>
2828
```
2929

pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.algorand</groupId>
66
<artifactId>algosdk</artifactId>
7-
<version>1.9.0</version>
7+
<version>1.10.0</version>
88
<packaging>jar</packaging>
99

1010
<name>${project.groupId}:${project.artifactId}</name>
@@ -55,17 +55,17 @@
5555
<dependency>
5656
<groupId>com.fasterxml.jackson.core</groupId>
5757
<artifactId>jackson-annotations</artifactId>
58-
<version>2.7.0</version>
58+
<version>2.9.9</version>
5959
</dependency>
6060
<dependency>
6161
<groupId>com.fasterxml.jackson.core</groupId>
6262
<artifactId>jackson-core</artifactId>
63-
<version>2.7.9</version>
63+
<version>2.9.9</version>
6464
</dependency>
6565
<dependency>
6666
<groupId>com.fasterxml.jackson.core</groupId>
6767
<artifactId>jackson-databind</artifactId>
68-
<version>2.7.9</version>
68+
<version>2.9.9.3</version>
6969
</dependency>
7070
<dependency>
7171
<groupId>com.google.code.gson</groupId>
@@ -117,7 +117,7 @@
117117
<dependency>
118118
<groupId>org.msgpack</groupId>
119119
<artifactId>jackson-dataformat-msgpack</artifactId>
120-
<version>0.8.16</version>
120+
<version>0.9.0</version>
121121
</dependency>
122122
<dependency>
123123
<groupId>org.threeten</groupId>

src/main/java/com/algorand/algosdk/account/Account.java

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)