Skip to content

Commit

Permalink
Merge branch 'release/v1.19.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
algobarb committed Aug 22, 2022
2 parents 0d7608a + 2f96979 commit ea72af2
Show file tree
Hide file tree
Showing 15 changed files with 500 additions and 51 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v1.19.1

### Enhancements

- API: Support attaching signatures to standard and multisig transactions by @jdtzmn in https://github.com/algorand/js-algorand-sdk/pull/595
- AVM: Consolidate TEAL and AVM versions by @michaeldiamant in https://github.com/algorand/js-algorand-sdk/pull/609
- Testing: Use Dev mode network for cucumber tests by @algochoi in https://github.com/algorand/js-algorand-sdk/pull/614

# v1.19.0

## What's Changed
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ unit:
node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js

integration:
node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c or @compile.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey_v1 or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c or @compile.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js

docker-test:
./tests/cucumber/docker/run_docker.sh
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Include a minified browser bundle directly in your HTML like so:

```html
<script
src="https://unpkg.com/[email protected].0/dist/browser/algosdk.min.js"
integrity="sha384-5cebCuqDx6A5Y1HeScIKIcSdqsub2M3wwkqTZyu45M8zN/+do8cgxcHDJjkaVTNb"
src="https://unpkg.com/[email protected].1/dist/browser/algosdk.min.js"
integrity="sha384-vpY7inPLTrCOYSwaOYlQbFwoSY/t3lFMVjAh/iXN+86fNAQ39DeQjlX87aczChqD"
crossorigin="anonymous"
></script>
```
Expand All @@ -32,8 +32,8 @@ or

```html
<script
src="https://cdn.jsdelivr.net/npm/[email protected].0/dist/browser/algosdk.min.js"
integrity="sha384-5cebCuqDx6A5Y1HeScIKIcSdqsub2M3wwkqTZyu45M8zN/+do8cgxcHDJjkaVTNb"
src="https://cdn.jsdelivr.net/npm/[email protected].1/dist/browser/algosdk.min.js"
integrity="sha384-vpY7inPLTrCOYSwaOYlQbFwoSY/t3lFMVjAh/iXN+86fNAQ39DeQjlX87aczChqD"
crossorigin="anonymous"
></script>
```
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "algosdk",
"version": "1.19.0",
"version": "1.19.1",
"description": "The official JavaScript SDK for Algorand",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
4 changes: 1 addition & 3 deletions src/logic/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,7 @@ export function readProgram(
}
// costs calculated dynamically starting in v4
if (version < 4 && cost > maxCost) {
throw new Error(
'program too costly for Teal version < 4. consider using v4.'
);
throw new Error('program too costly for version < 4. consider using v4.');
}
return [ints, byteArrays, true];
}
Expand Down
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export {
signMultisigTransaction,
mergeMultisigTransactions,
appendSignMultisigTransaction,
createMultisigTransaction,
appendSignRawMultisigSignature,
verifyMultisig,
multisigAddress,
} from './multisig';
export { SourceMap } from './logic/sourcemap';
Expand Down
154 changes: 127 additions & 27 deletions src/multisig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const MULTISIG_NO_MUTATE_ERROR_MSG =
'Cannot mutate a multisig field as it would invalidate all existing signatures.';
export const MULTISIG_USE_PARTIAL_SIGN_ERROR_MSG =
'Cannot sign a multisig transaction using `signTxn`. Use `partialSignTxn` instead.';
export const MULTISIG_SIGNATURE_LENGTH_ERROR_MSG =
'Cannot add multisig signature. Signature is not of the correct length.';

interface MultisigOptions {
rawSig: Uint8Array;
Expand All @@ -40,41 +42,27 @@ interface MultisigMetadataWithPks extends Omit<MultisigMetadata, 'addrs'> {
}

/**
* createMultisigTransaction creates a multisig transaction blob.
* @param txnForEncoding - the actual transaction to sign.
* @param rawSig - a Buffer raw signature of that transaction
* @param myPk - a public key that corresponds with rawSig
* createRawMultisigTransaction creates a raw, unsigned multisig transaction blob.
* @param txn - the actual transaction.
* @param version - multisig version
* @param threshold - mutlisig threshold
* @param threshold - multisig threshold
* @param pks - ordered list of public keys in this multisig
* @returns encoded multisig blob
*/
function createMultisigTransaction(
txnForEncoding: EncodedTransaction,
{ rawSig, myPk }: MultisigOptions,
{ version, threshold, pks }: MultisigMetadataWithPks
export function createMultisigTransaction(
txn: txnBuilder.Transaction,
{ version, threshold, addrs }: MultisigMetadata
) {
let keyExist = false;
// construct the appendable multisigned transaction format
const subsigs = pks.map((pk) => {
if (nacl.bytesEqual(pk, myPk)) {
keyExist = true;
return {
pk: Buffer.from(pk),
s: rawSig,
};
}
return { pk: Buffer.from(pk) };
});
if (keyExist === false) {
throw new Error(MULTISIG_KEY_NOT_EXIST_ERROR_MSG);
}
const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey);
const subsigs = pks.map((pk) => ({ pk: Buffer.from(pk) }));

const msig: EncodedMultisig = {
v: version,
thr: threshold,
subsig: subsigs,
};
const txnForEncoding = txn.get_obj_for_encoding();
const signedTxn: EncodedSignedTransaction = {
msig,
txn: txnForEncoding,
Expand All @@ -97,6 +85,58 @@ function createMultisigTransaction(
return new Uint8Array(encoding.encode(signedTxn));
}

/**
* createMultisigTransactionWithSignature creates a multisig transaction blob with an included signature.
* @param txn - the actual transaction to sign.
* @param rawSig - a Buffer raw signature of that transaction
* @param myPk - a public key that corresponds with rawSig
* @param version - multisig version
* @param threshold - multisig threshold
* @param pks - ordered list of public keys in this multisig
* @returns encoded multisig blob
*/
function createMultisigTransactionWithSignature(
txn: txnBuilder.Transaction,
{ rawSig, myPk }: MultisigOptions,
{ version, threshold, pks }: MultisigMetadataWithPks
) {
// Create an empty encoded multisig transaction
const encodedMsig = createMultisigTransaction(txn, {
version,
threshold,
addrs: pks.map((pk) => address.encodeAddress(pk)),
});
// note: this is not signed yet, but will be shortly
const signedTxn = encoding.decode(encodedMsig) as EncodedSignedTransaction;

let keyExist = false;
// append the multisig signature to the corresponding public key in the multisig blob
signedTxn.msig.subsig.forEach((subsig, i) => {
if (nacl.bytesEqual(subsig.pk, myPk)) {
keyExist = true;
signedTxn.msig.subsig[i].s = rawSig;
}
});
if (keyExist === false) {
throw new Error(MULTISIG_KEY_NOT_EXIST_ERROR_MSG);
}

// if the address of this multisig is different from the transaction sender,
// we need to add the auth-addr field
const msigAddr = address.fromMultisigPreImg({
version,
threshold,
pks,
});
if (
address.encodeAddress(signedTxn.txn.snd) !== address.encodeAddress(msigAddr)
) {
signedTxn.sgnr = Buffer.from(msigAddr);
}

return new Uint8Array(encoding.encode(signedTxn));
}

/**
* MultisigTransaction is a Transaction that also supports creating partially-signed multisig transactions.
*/
Expand Down Expand Up @@ -140,13 +180,39 @@ export class MultisigTransaction extends txnBuilder.Transaction {
) {
// get signature verifier
const myPk = nacl.keyPairFromSecretKey(sk).publicKey;
return createMultisigTransaction(
this.get_obj_for_encoding(),
return createMultisigTransactionWithSignature(
this,
{ rawSig: this.rawSignTxn(sk), myPk },
{ version, threshold, pks }
);
}

/**
* partialSignWithMultisigSignature partially signs this transaction with an external raw multisig signature and returns
* a partially-signed multisig transaction, encoded with msgpack as a typed array.
* @param metadata - multisig metadata
* @param signerAddr - address of the signer
* @param signature - raw multisig signature
* @returns an encoded, partially signed multisig transaction.
*/
partialSignWithMultisigSignature(
metadata: MultisigMetadataWithPks,
signerAddr: string,
signature: Uint8Array
) {
if (!nacl.isValidSignatureLength(signature.length)) {
throw new Error(MULTISIG_SIGNATURE_LENGTH_ERROR_MSG);
}
return createMultisigTransactionWithSignature(
this,
{
rawSig: signature,
myPk: address.decodeAddress(signerAddr).publicKey,
},
metadata
);
}

// eslint-disable-next-line camelcase
static from_obj_for_encoding(
txnForEnc: EncodedTransaction
Expand Down Expand Up @@ -312,7 +378,7 @@ export function verifyMultisig(
/**
* signMultisigTransaction takes a raw transaction (see signTransaction), a multisig preimage, a secret key, and returns
* a multisig transaction, which is a blob representing a transaction and multisignature account preimage. The returned
* multisig txn can accumulate additional signatures through mergeMultisigTransactions or appendMultisigTransaction.
* multisig txn can accumulate additional signatures through mergeMultisigTransactions or appendSignMultisigTransaction.
* @param txn - object with either payment or key registration fields
* @param version - multisig version
* @param threshold - multisig threshold
Expand Down Expand Up @@ -391,9 +457,43 @@ export function appendSignMultisigTransaction(
};
}

/**
* appendMultisigTransactionSignature takes a multisig transaction blob, and appends a given raw signature to it.
* This makes it possible to compile a multisig signature using only raw signatures from external methods.
* @param multisigTxnBlob - an encoded multisig txn. Supports non-payment txn types.
* @param version - multisig version
* @param threshold - multisig threshold
* @param addrs - a list of Algorand addresses representing possible signers for this multisig. Order is important.
* @param signerAddr - address of the signer
* @param signature - raw multisig signature
* @returns object containing txID, and blob representing encoded multisig txn
*/
export function appendSignRawMultisigSignature(
multisigTxnBlob: Uint8Array,
{ version, threshold, addrs }: MultisigMetadata,
signerAddr: string,
signature: Uint8Array
) {
const pks = addrs.map((addr) => address.decodeAddress(addr).publicKey);
// obtain underlying txn, sign it, and merge it
const multisigTxObj = encoding.decode(
multisigTxnBlob
) as EncodedSignedTransaction;
const msigTxn = MultisigTransaction.from_obj_for_encoding(multisigTxObj.txn);
const partialSignedBlob = msigTxn.partialSignWithMultisigSignature(
{ version, threshold, pks },
signerAddr,
signature
);
return {
txID: msigTxn.txID().toString(),
blob: mergeMultisigTransactions([multisigTxnBlob, partialSignedBlob]),
};
}

/**
* multisigAddress takes multisig metadata (preimage) and returns the corresponding human readable Algorand address.
* @param version - mutlisig version
* @param version - multisig version
* @param threshold - multisig threshold
* @param addrs - list of Algorand addresses
*/
Expand Down
4 changes: 4 additions & 0 deletions src/nacl/naclWrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export function keyPair() {
return keyPairFromSeed(seed);
}

export function isValidSignatureLength(len: number) {
return len === nacl.sign.signatureLength;
}

export function keyPairFromSecretKey(sk: Uint8Array) {
return nacl.sign.keyPair.fromSecretKey(sk);
}
Expand Down
16 changes: 16 additions & 0 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,22 @@ export class Transaction implements TransactionStorageStructure {
return new Uint8Array(encoding.encode(sTxn));
}

attachSignature(signerAddr: string, signature: Uint8Array) {
if (!nacl.isValidSignatureLength(signature.length)) {
throw new Error('Invalid signature length');
}
const sTxn: EncodedSignedTransaction = {
sig: Buffer.from(signature),
txn: this.get_obj_for_encoding(),
};
// add AuthAddr if signing with a different key than From indicates
if (signerAddr !== address.encodeAddress(this.from.publicKey)) {
const signerPublicKey = address.decodeAddress(signerAddr).publicKey;
sTxn.sgnr = Buffer.from(signerPublicKey);
}
return new Uint8Array(encoding.encode(sTxn));
}

rawTxID() {
const enMsg = this.toByte();
const gh = Buffer.from(utils.concatArrays(this.tag, enMsg));
Expand Down
8 changes: 8 additions & 0 deletions tests/4.Utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'assert';
import * as utils from '../src/utils/utils';
import * as nacl from '../src/nacl/naclWrappers';

describe('utils', () => {
describe('concatArrays', () => {
Expand Down Expand Up @@ -33,3 +34,10 @@ describe('utils', () => {
});
});
});

describe('nacl wrapper', () => {
it('should validate signature length', () => {
assert.strictEqual(nacl.isValidSignatureLength(6), false);
assert.strictEqual(nacl.isValidSignatureLength(64), true);
});
});
Loading

0 comments on commit ea72af2

Please sign in to comment.