feat: implement addSignature in VersionedTransaction (#27945)

* Implement addSignature in VersionedTransaction

* Update asserts

* VersionedTransaction.addSignature test

* Update VersionedTransaction tests
This commit is contained in:
Varga-Somogyi Ákos 2022-09-22 02:22:40 +02:00 committed by GitHub
parent e1a49f7766
commit e15a5c010e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 20 deletions

View File

@ -7,6 +7,7 @@ import {SIGNATURE_LENGTH_IN_BYTES} from './constants';
import * as shortvec from '../utils/shortvec-encoding';
import * as Layout from '../layout';
import {sign} from '../utils/ed25519';
import {PublicKey} from '../publickey';
export type TransactionVersion = 'legacy' | 0;
@ -106,4 +107,20 @@ export class VersionedTransaction {
this.signatures[signerIndex] = sign(messageData, signer.secretKey);
}
}
addSignature(publicKey: PublicKey, signature: Uint8Array) {
assert(signature.byteLength === 64, 'Signature must be 64 bytes long');
const signerPubkeys = this.message.staticAccountKeys.slice(
0,
this.message.header.numRequiredSignatures,
);
const signerIndex = signerPubkeys.findIndex(pubkey =>
pubkey.equals(publicKey),
);
assert(
signerIndex >= 0,
`Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`,
);
this.signatures[signerIndex] = signature;
}
}

View File

@ -8,6 +8,7 @@ import {PublicKey} from '../src/publickey';
import {
Transaction,
TransactionInstruction,
TransactionMessage,
VersionedTransaction,
} from '../src/transaction';
import {StakeProgram, SystemProgram} from '../src/programs';
@ -860,26 +861,6 @@ describe('Transaction', () => {
expect(tx.verifySignatures()).to.be.true;
});
it('deserializes versioned transactions', () => {
const serializedVersionedTx = Buffer.from(
'AdTIDASR42TgVuXKkd7mJKk373J3LPVp85eyKMVcrboo9KTY8/vm6N/Cv0NiHqk2I8iYw6VX5ZaBKG8z' +
'9l1XjwiAAQACA+6qNbqfjaIENwt9GzEK/ENiB/ijGwluzBUmQ9xlTAMcCaS0ctnyxTcXXlJr7u2qtnaM' +
'gIAO2/c7RBD0ipHWUcEDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJbI7VNs6MzREUlnzRaJ' +
'pBKP8QQoDn2dWQvD0KIgHFDiAwIACQAgoQcAAAAAAAIABQEAAAQAATYPBwAKBDIBAyQWIw0oCxIdCA4i' +
'JzQRKwUZHxceHCohMBUJJiwpMxAaGC0TLhQxGyAMBiU2NS8VDgAAAADuAgAAAAAAAAIAAAAAAAAAAdGCT' +
'Qiq5yw3+3m1sPoRNj0GtUNNs0FIMocxzt3zuoSZHQABAwQFBwgLDA8RFBcYGhwdHh8iIyUnKiwtLi8yF' +
'wIGCQoNDhASExUWGRsgISQmKCkrMDEz',
'base64',
);
expect(() => Transaction.from(serializedVersionedTx)).to.throw(
'Versioned messages must be deserialized with VersionedMessage.deserialize()',
);
const versionedTx = VersionedTransaction.deserialize(serializedVersionedTx);
expect(versionedTx.message.version).to.eq(0);
});
it('can serialize, deserialize, and reserialize with a partial signer', () => {
const signer = Keypair.generate();
const acc0Writable = Keypair.generate();
@ -963,3 +944,97 @@ describe('Transaction', () => {
t1.serialize();
});
});
describe('VersionedTransaction', () => {
it('deserializes versioned transactions', () => {
const serializedVersionedTx = Buffer.from(
'AdTIDASR42TgVuXKkd7mJKk373J3LPVp85eyKMVcrboo9KTY8/vm6N/Cv0NiHqk2I8iYw6VX5ZaBKG8z' +
'9l1XjwiAAQACA+6qNbqfjaIENwt9GzEK/ENiB/ijGwluzBUmQ9xlTAMcCaS0ctnyxTcXXlJr7u2qtnaM' +
'gIAO2/c7RBD0ipHWUcEDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJbI7VNs6MzREUlnzRaJ' +
'pBKP8QQoDn2dWQvD0KIgHFDiAwIACQAgoQcAAAAAAAIABQEAAAQAATYPBwAKBDIBAyQWIw0oCxIdCA4i' +
'JzQRKwUZHxceHCohMBUJJiwpMxAaGC0TLhQxGyAMBiU2NS8VDgAAAADuAgAAAAAAAAIAAAAAAAAAAdGCT' +
'Qiq5yw3+3m1sPoRNj0GtUNNs0FIMocxzt3zuoSZHQABAwQFBwgLDA8RFBcYGhwdHh8iIyUnKiwtLi8yF' +
'wIGCQoNDhASExUWGRsgISQmKCkrMDEz',
'base64',
);
expect(() => Transaction.from(serializedVersionedTx)).to.throw(
'Versioned messages must be deserialized with VersionedMessage.deserialize()',
);
const versionedTx = VersionedTransaction.deserialize(serializedVersionedTx);
expect(versionedTx.message.version).to.eq(0);
});
describe('addSignature', () => {
const signer1 = Keypair.generate();
const signer2 = Keypair.generate();
const signer3 = Keypair.generate();
const recentBlockhash = new PublicKey(3).toBuffer();
const message = new TransactionMessage({
payerKey: signer1.publicKey,
instructions: [
new TransactionInstruction({
data: Buffer.from('Hello!'),
keys: [
{
pubkey: signer1.publicKey,
isSigner: true,
isWritable: true,
},
{
pubkey: signer2.publicKey,
isSigner: true,
isWritable: true,
},
{
pubkey: signer3.publicKey,
isSigner: false,
isWritable: false,
},
],
programId: new PublicKey(
'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr',
),
}),
],
recentBlockhash: bs58.encode(recentBlockhash),
});
const transaction = new VersionedTransaction(message.compileToV0Message());
it('appends externally generated signatures at correct indexes', () => {
const signature1 = sign(
transaction.message.serialize(),
signer1.secretKey,
);
const signature2 = sign(
transaction.message.serialize(),
signer2.secretKey,
);
transaction.addSignature(signer2.publicKey, signature2);
transaction.addSignature(signer1.publicKey, signature1);
expect(transaction.signatures).to.have.length(2);
expect(transaction.signatures[0]).to.eq(signature1);
expect(transaction.signatures[1]).to.eq(signature2);
});
it('fatals when the signature is the wrong length', () => {
expect(() => {
transaction.addSignature(signer1.publicKey, new Uint8Array(32));
}).to.throw('Signature must be 64 bytes long');
});
it('fatals when adding a signature for a public key that has not been marked as a signer', () => {
expect(() => {
transaction.addSignature(signer3.publicKey, new Uint8Array(64));
}).to.throw(
`Can not add signature; \`${signer3.publicKey.toBase58()}\` is not required to sign this transaction`,
);
});
});
});