Skip to content

Commit 736ad61

Browse files
authored
Signatures: support lmsig logicsig signatures (#865)
1 parent 9719631 commit 736ad61

File tree

7 files changed

+217
-47
lines changed

7 files changed

+217
-47
lines changed

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ public LogicsigSignature signLogicsig(LogicsigSignature lsig, MultisigAddress ma
478478
// now, create the multisignature
479479
Signature sig;
480480
try {
481-
byte[] bytesToSign = lsig.bytesToSign();
481+
byte[] bytesToSign = lsig.bytesToSignMultisig(ma.toAddress());
482482
sig = this.rawSignBytes(bytesToSign);
483483
} catch (NoSuchAlgorithmException ex) {
484484
throw new IOException("could not sign transaction", ex);
@@ -492,7 +492,7 @@ public LogicsigSignature signLogicsig(LogicsigSignature lsig, MultisigAddress ma
492492
mSig.subsigs.add(new MultisigSubsig(ma.publicKeys.get(i)));
493493
}
494494
}
495-
lsig.msig = mSig;
495+
lsig.lmsig = mSig;
496496
return lsig;
497497
}
498498

@@ -504,10 +504,13 @@ public LogicsigSignature signLogicsig(LogicsigSignature lsig, MultisigAddress ma
504504
* @throws NoSuchAlgorithmException
505505
*/
506506
public LogicsigSignature appendToLogicsig(LogicsigSignature lsig) throws IllegalArgumentException, IOException {
507+
if (lsig.lmsig == null) {
508+
throw new IllegalArgumentException("LogicsigSignature.lmsig is null; cannot append to multisig logic signature.");
509+
}
507510
Ed25519PublicKey myPK = this.getEd25519PublicKey();
508511
int myIndex = -1;
509-
for (int i = 0; i < lsig.msig.subsigs.size(); i++ ) {
510-
MultisigSubsig subsig = lsig.msig.subsigs.get(i);
512+
for (int i = 0; i < lsig.lmsig.subsigs.size(); i++ ) {
513+
MultisigSubsig subsig = lsig.lmsig.subsigs.get(i);
511514
if (subsig.key.equals(myPK)) {
512515
myIndex = i;
513516
}
@@ -518,9 +521,10 @@ public LogicsigSignature appendToLogicsig(LogicsigSignature lsig) throws Illegal
518521

519522
try {
520523
// now, create the multisignature
521-
byte[] bytesToSign = lsig.bytesToSign();
524+
Address multisigAddr = lsig.lmsig.convertToMultisigAddress().toAddress();
525+
byte[] bytesToSign = lsig.bytesToSignMultisig(multisigAddr);
522526
Signature sig = this.rawSignBytes(bytesToSign);
523-
lsig.msig.subsigs.set(myIndex, new MultisigSubsig(myPK, sig));
527+
lsig.lmsig.subsigs.set(myIndex, new MultisigSubsig(myPK, sig));
524528
return lsig;
525529
} catch (NoSuchAlgorithmException ex) {
526530
throw new IOException("could not sign transaction", ex);
@@ -556,11 +560,14 @@ public static SignedTransaction signLogicTransactionWithAddress(LogicsigSignatur
556560
*/
557561
public static SignedTransaction signLogicsigTransaction(LogicsigSignature lsig, Transaction tx) throws IllegalArgumentException, IOException {
558562
boolean hasSig = lsig.sig != null;
563+
boolean hasLmsig = lsig.lmsig != null;
559564
boolean hasMsig = lsig.msig != null;
560565
Address lsigAddr;
561566
try {
562567
if (hasSig) {
563568
lsigAddr = tx.sender;
569+
} else if (hasLmsig) {
570+
lsigAddr = lsig.lmsig.convertToMultisigAddress().toAddress();
564571
} else if (hasMsig) {
565572
lsigAddr = lsig.msig.convertToMultisigAddress().toAddress();
566573
} else {

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ public void appendMultiSig(PrivateKey privateKey)
9595
public LogicSigAccount(LogicsigSignature lsig, Ed25519PublicKey signerPublicKey)
9696
throws IllegalArgumentException, NoSuchAlgorithmException {
9797
boolean hasSig = lsig.sig != null;
98+
boolean hasLmsig = lsig.lmsig != null;
9899
boolean hasMsig = lsig.msig != null;
99-
if (hasSig && hasMsig)
100-
throw new IllegalArgumentException("Logicsig has too many signatures, at most one of Sig or Msig may be defined");
100+
101+
if (lsig.sigCount() > 1)
102+
throw new IllegalArgumentException("Logicsig has too many signatures, at most one of Sig, Msig, or LMsig may be defined");
101103
if (hasSig) {
102104
if (signerPublicKey == null)
103105
throw new IllegalArgumentException("Cannot generate LogicSigAccount from single-signed LogicSig and a null public key");
@@ -119,8 +121,9 @@ public LogicSigAccount(LogicsigSignature lsig, Ed25519PublicKey signerPublicKey)
119121
*/
120122
public boolean isDelegated() {
121123
boolean hasSig = this.lsig.sig != null;
124+
boolean hasLmsig = this.lsig.lmsig != null;
122125
boolean hasMsig = this.lsig.msig != null;
123-
return hasSig || hasMsig;
126+
return hasSig || hasLmsig || hasMsig;
124127
}
125128

126129
/**
@@ -131,13 +134,22 @@ public boolean isDelegated() {
131134
*/
132135
public Address getAddress() throws NoSuchAlgorithmException, IllegalArgumentException {
133136
boolean hasSig = this.lsig.sig != null;
137+
boolean hasLmsig = this.lsig.lmsig != null;
134138
boolean hasMsig = this.lsig.msig != null;
135-
if (hasSig && hasMsig)
136-
throw new IllegalArgumentException("Logicsig has too many signatures, at most one of Sig or Msig may be defined");
139+
140+
if (this.lsig.sigCount() > 1)
141+
throw new IllegalArgumentException("Logicsig has too many signatures, at most one of Sig, Msig, or LMsig may be defined");
137142
if (hasSig) {
138143
byte[] sigKeyRaw = this.sigKey.getBytes();
139144
return new Address(sigKeyRaw);
140145
}
146+
if (hasLmsig) {
147+
List<Ed25519PublicKey> pkFromSubSig = new ArrayList<>();
148+
for (MultisigSignature.MultisigSubsig subSig : this.lsig.lmsig.subsigs)
149+
pkFromSubSig.add(subSig.key);
150+
MultisigAddress ma = new MultisigAddress(this.lsig.lmsig.version, this.lsig.lmsig.threshold, pkFromSubSig);
151+
return ma.toAddress();
152+
}
141153
if (hasMsig) {
142154
List<Ed25519PublicKey> pkFromSubSig = new ArrayList<>();
143155
for (MultisigSignature.MultisigSubsig subSig : this.lsig.msig.subsigs)

src/main/java/com/algorand/algosdk/crypto/LogicsigSignature.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public class LogicsigSignature {
2929
@JsonIgnore
3030
private static final byte[] LOGIC_PREFIX = ("Program").getBytes(StandardCharsets.UTF_8);
3131
@JsonIgnore
32+
private static final byte[] MULTISIG_PROGRAM_PREFIX = ("MsigProgram").getBytes(StandardCharsets.UTF_8);
33+
@JsonIgnore
3234
private static final String SIGN_ALGO = "EdDSA";
3335

3436
@JsonProperty("l")
@@ -39,6 +41,8 @@ public class LogicsigSignature {
3941
public Signature sig;
4042
@JsonProperty("msig")
4143
public MultisigSignature msig;
44+
@JsonProperty("lmsig")
45+
public MultisigSignature lmsig;
4246

4347

4448
private static boolean isAsciiPrintable(final byte symbol) {
@@ -98,7 +102,8 @@ public LogicsigSignature(
98102
@JsonProperty("l") byte[] logic,
99103
@JsonProperty("arg") List<byte[]> args,
100104
@JsonProperty("sig") byte[] sig,
101-
@JsonProperty("msig") MultisigSignature msig
105+
@JsonProperty("msig") MultisigSignature msig,
106+
@JsonProperty("lmsig") MultisigSignature lmsig
102107
) {
103108
this.logic = Objects.requireNonNull(logic, "program must not be null");
104109
this.args = args;
@@ -107,6 +112,7 @@ public LogicsigSignature(
107112

108113
if (sig != null) this.sig = new Signature(sig);
109114
this.msig = msig;
115+
this.lmsig = lmsig;
110116
}
111117

112118
/**
@@ -123,7 +129,7 @@ public LogicsigSignature(byte[] logicsig) {
123129
* @param args
124130
*/
125131
public LogicsigSignature(byte[] logicsig, List<byte[]> args) {
126-
this(logicsig, args, null, null);
132+
this(logicsig, args, null, null, null);
127133
}
128134

129135
/**
@@ -134,6 +140,19 @@ public LogicsigSignature() {
134140
this.args = null;
135141
}
136142

143+
/**
144+
* Returns the number of signatures (sig, msig, lmsig) on this LogicSig.
145+
* At most one of these should be present.
146+
* @return the number of signature types present
147+
*/
148+
public int sigCount() {
149+
int count = 0;
150+
if (this.sig != null) count++;
151+
if (this.msig != null) count++;
152+
if (this.lmsig != null) count++;
153+
return count;
154+
}
155+
137156
/**
138157
* Calculate escrow address from logic sig program
139158
* NOTE: THIS RETURNS AN ESCROW ACCOUNT OF A LOGIC-SIG (FROM LOGIC ITSELF),
@@ -157,6 +176,21 @@ public byte[] bytesToSign() {
157176
return prefixedEncoded;
158177
}
159178

179+
/**
180+
* Return prefixed program with multisig address as byte array that is ready to use with multisig
181+
* signatures on delegated logicsig programs
182+
* @param multisigAddress the multisig address to include in signed data
183+
* @return byte[]
184+
*/
185+
public byte[] bytesToSignMultisig(Address multisigAddress) {
186+
byte[] addressBytes = multisigAddress.getBytes();
187+
byte[] prefixedEncoded = new byte[this.logic.length + MULTISIG_PROGRAM_PREFIX.length + addressBytes.length];
188+
System.arraycopy(MULTISIG_PROGRAM_PREFIX, 0, prefixedEncoded, 0, MULTISIG_PROGRAM_PREFIX.length);
189+
System.arraycopy(addressBytes, 0, prefixedEncoded, MULTISIG_PROGRAM_PREFIX.length, addressBytes.length);
190+
System.arraycopy(this.logic, 0, prefixedEncoded, MULTISIG_PROGRAM_PREFIX.length + addressBytes.length, this.logic.length);
191+
return prefixedEncoded;
192+
}
193+
160194
/**
161195
* Perform signature verification against the sender address
162196
* @param singleSigner only used in the case of a delegated LogicSig whose delegating account
@@ -168,7 +202,8 @@ public boolean verify(Address singleSigner) throws NoSuchAlgorithmException {
168202
return false;
169203
}
170204

171-
if (this.sig != null && this.msig != null) {
205+
// At most one signature type should be present
206+
if (sigCount() > 1) {
172207
return false;
173208
}
174209

@@ -191,6 +226,11 @@ public boolean verify(Address singleSigner) throws NoSuchAlgorithmException {
191226
}
192227
}
193228

229+
if (this.lmsig != null) {
230+
Address multisigAddr = this.lmsig.convertToMultisigAddress().toAddress();
231+
return this.lmsig.verify(this.bytesToSignMultisig(multisigAddr));
232+
}
233+
194234
if (this.msig != null)
195235
return this.msig.verify(this.bytesToSign());
196236

@@ -221,6 +261,9 @@ public boolean equals(Object obj) {
221261
if (!LogicsigSignature.nullCheck(this.sig, actual.sig)) return false;
222262
if (this.sig != null && !this.sig.equals(actual.sig)) return false;
223263

264+
if (!LogicsigSignature.nullCheck(this.lmsig, actual.lmsig)) return false;
265+
if (this.lmsig != null && !this.lmsig.equals(actual.lmsig)) return false;
266+
224267
if (!LogicsigSignature.nullCheck(this.msig, actual.msig)) return false;
225268
return this.msig == null || this.msig.equals(actual.msig);
226269
} else {

0 commit comments

Comments
 (0)