Skip to content

Commit

Permalink
Merge pull request #17 from radixdlt/feature/lts-builder-multiple-sig…
Browse files Browse the repository at this point in the history
…ners

Feature: Allow multiple intent signatures in LTS builder.
  • Loading branch information
0xOmarA authored Jun 12, 2023
2 parents 7b91b24 + a15a388 commit d78e427
Show file tree
Hide file tree
Showing 10 changed files with 1,805 additions and 2,776 deletions.
310 changes: 227 additions & 83 deletions LTS.md

Large diffs are not rendered by default.

38 changes: 28 additions & 10 deletions examples/core-e2e-example/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
NetworkId,
PrivateKey,
SimpleTransactionBuilder,
Signature,
SignatureWithPublicKey
} from "@radixdlt/radix-engine-toolkit";
import fetch from "node-fetch"; // 2.6.9
import { default as http, default as https } from "node:http";
Expand Down Expand Up @@ -106,7 +108,6 @@ async function getTestnetXrd(
const constructionMetadata =
await coreApiClient.LTS.getConstructionMetadata();

const notary = await generateNewEd25519VirtualAccount(networkId);
const freeXrdForAccount1Transaction =
await SimpleTransactionBuilder.freeXrdFromFaucet({
networkId,
Expand All @@ -126,13 +127,15 @@ async function getTestnetXrd(
}

const main = async () => {
const feePayer = await generateNewEd25519VirtualAccount(networkId);
const account1 = await generateNewEd25519VirtualAccount(networkId);
const account2 = await generateNewEd25519VirtualAccount(networkId);
const knownAddresses = await LTSRadixEngineToolkit.Derive.knownAddresses(
networkId
);
const xrd = knownAddresses.resources.xrdResource;

console.log(`Fee Payer: ${feePayer.dashboardLink}`);
console.log(`Account 1: ${account1.dashboardLink}`);
console.log(`Account 2: ${account2.dashboardLink}`);

Expand All @@ -154,6 +157,15 @@ const main = async () => {
`Account 1 has been topped up with 10000 Testnet XRD: ${dashboardBase}/transaction/${faucetTransactionIntentHash}`
);

const faucetTransaction2IntentHash = await getTestnetXrd(
coreApiClient,
feePayer.address
);

console.log(
`Fee payer has been topped up with 10000 Testnet XRD: ${dashboardBase}/transaction/${faucetTransaction2IntentHash}`
);

const constructionMetadata =
await coreApiClient.LTS.getConstructionMetadata();
const builder = await SimpleTransactionBuilder.new({
Expand All @@ -164,20 +176,26 @@ const main = async () => {
});

// Note - by default this sets to permanently reject after 2 epochs (5-10 minutes)
const unsignedTransaction = builder
const signedIntent = await builder
.transferFungible({
toAccount: account2.address,
resourceAddress: xrd,
amount: 100,
})
.compileIntent();

const notarySignature = account1.privateKey.signToSignature(
unsignedTransaction.hashToNotarize
);

const notarizedTransaction =
unsignedTransaction.compileNotarized(notarySignature);
// NOTE - if not using a separate fee payer, you can remove this line
.feePayer(feePayer.address)
.compileIntentWithSignaturesAsync([
// NOTE - if not using a separate fee payer, you can use an empty array here
async (hash: Uint8Array): Promise<SignatureWithPublicKey.SignatureWithPublicKey> => {
return feePayer.privateKey.signToSignatureWithPublicKey(hash);
}
]);
const notarizedTransaction = await signedIntent
.compileNotarizedAsync(async (hash: Uint8Array): Promise<Signature.Signature> => {
return account1.privateKey.signToSignature(hash);
});

(await notarizedTransaction.staticallyValidate(networkId)).throwIfInvalid();

const intentHashHex = notarizedTransaction.intentHashHex();

Expand Down
46 changes: 46 additions & 0 deletions src/builders/builder_models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,32 @@ export function resolveDecimal(amount: Amount): Decimal {
export type SignFn = (
hashToSign: Uint8Array
) => SignatureWithPublicKey.SignatureWithPublicKey;
export type AsyncSignFn = (
hashToSign: Uint8Array
) => Promise<SignatureWithPublicKey.SignatureWithPublicKey>;

export type NotarizeFn = (hashToSign: Uint8Array) => Signature.Signature;
export type AsyncNotarizeFn = (
hashToSign: Uint8Array
) => Promise<Signature.Signature>;

export type SignatureSource =
| IPrivateKey
| SignFn
| SignatureWithPublicKey.SignatureWithPublicKey;
export type AsyncSignatureSource =
| IPrivateKey
| AsyncSignFn
| SignatureWithPublicKey.SignatureWithPublicKey;

export type NotarySignatureSource =
| IPrivateKey
| NotarizeFn
| Signature.Signature;
export type NotarySignatureSourceAsync =
| IPrivateKey
| AsyncNotarizeFn
| Signature.Signature;

export function resolveSignature(
source: SignatureSource,
Expand All @@ -49,6 +65,21 @@ export function resolveSignature(
}
}

export async function resolveSignatureAsync(
source: AsyncSignatureSource,
hashToSign: Uint8Array
): Promise<SignatureWithPublicKey.SignatureWithPublicKey> {
if (typeof source === "function") {
return source(hashToSign);
} else if (source instanceof SignatureWithPublicKey.SignatureWithPublicKey) {
return Promise.resolve(source);
} else if (typeof source.signToSignatureWithPublicKey === "function") {
return Promise.resolve(source.signToSignatureWithPublicKey(hashToSign));
} else {
throw new TypeError("Invalid type passed in for signature source");
}
}

export function resolveNotarySignature(
source: NotarySignatureSource,
hashToNotarize: Uint8Array
Expand All @@ -63,3 +94,18 @@ export function resolveNotarySignature(
throw new TypeError("Invalid type passed in for signature source");
}
}

export async function resolveNotarySignatureAsync(
source: NotarySignatureSourceAsync,
hashToNotarize: Uint8Array
): Promise<Signature.Signature> {
if (typeof source === "function") {
return source(hashToNotarize);
} else if (source instanceof Signature.Signature) {
return Promise.resolve(source);
} else if (typeof source.signToSignature === "function") {
return Promise.resolve(source.signToSignature(hashToNotarize));
} else {
throw new TypeError("Invalid type passed in for signature source");
}
}
87 changes: 86 additions & 1 deletion src/builders/simple_transaction_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@ import { RadixEngineToolkitWasmWrapper } from "../wrapper/wasm_wrapper";
import {
Address,
Amount,
AsyncSignatureSource,
NotarySignatureSource,
NotarySignatureSourceAsync,
SignatureSource,
resolveAddress,
resolveDecimal,
resolveNotarySignature,
resolveNotarySignatureAsync,
resolveSignatureAsync,
} from "./builder_models";
import { TransactionBuilderIntentSignaturesStep } from "./transaction_builder";

Expand Down Expand Up @@ -80,6 +85,7 @@ export class SimpleTransactionBuilder {
private _notaryPublicKey: PublicKey.PublicKey;

private _fromAccount: string;
private _feePayer: string;
private _feeAmount: Decimal | undefined;
private _actions: Array<Action> = [];

Expand All @@ -95,6 +101,7 @@ export class SimpleTransactionBuilder {
this._startEpoch = startEpoch;
this._networkId = networkId;
this._fromAccount = fromAccount;
this._feePayer = fromAccount;
this._nonce = nonce;
this._notaryPublicKey = notaryPublicKey;
}
Expand Down Expand Up @@ -192,6 +199,11 @@ export class SimpleTransactionBuilder {
return this;
}

feePayer(address: string): this {
this._feePayer = address;
return this;
}

/**
* Set the number of epochs this transaction is valid for (including the current epoch - which might nearly be over!)
* Each epoch is approximately 5 minutes long.
Expand Down Expand Up @@ -261,6 +273,54 @@ export class SimpleTransactionBuilder {
);
}

public compileIntentWithSignatures(
signatureSources: Array<SignatureSource>
): CompiledSignedTransactionIntent {
const transitioned = this.transition();
const { intentHash } = transitioned.compileIntent();

for (const signatureSource of signatureSources) {
transitioned.sign(signatureSource);
}

const { compiledSignedIntent, signedIntent, signedIntentHash } =
transitioned.compileSignedIntent();

return new CompiledSignedTransactionIntent(
this.retWrapper,
intentHash,
signedIntent,
compiledSignedIntent,
signedIntentHash
);
}

public async compileIntentWithSignaturesAsync(
signatureSources: Array<AsyncSignatureSource>
): Promise<CompiledSignedTransactionIntent> {
const transitioned = this.transition();
const { intentHash } = transitioned.compileIntent();

for (const signatureSource of signatureSources) {
const signature = await resolveSignatureAsync(
signatureSource,
intentHash
);
transitioned.sign(signature);
}

const { compiledSignedIntent, signedIntent, signedIntentHash } =
transitioned.compileSignedIntent();

return new CompiledSignedTransactionIntent(
this.retWrapper,
intentHash,
signedIntent,
compiledSignedIntent,
signedIntentHash
);
}

//=================
// Private Methods
//=================
Expand Down Expand Up @@ -296,7 +356,7 @@ export class SimpleTransactionBuilder {

instructions.push(
new Instruction.CallMethod(
new ManifestAstValue.Address(this._fromAccount),
new ManifestAstValue.Address(this._feePayer),
new ManifestAstValue.String("lock_fee"),
[new ManifestAstValue.Decimal(feeAmount)]
)
Expand Down Expand Up @@ -483,6 +543,31 @@ export class CompiledSignedTransactionIntent {
);
}

async compileNotarizedAsync(
source: NotarySignatureSourceAsync
): Promise<CompiledNotarizedTransaction> {
let notarizedTransaction = new NotarizedTransaction(
this.signedIntent,
await resolveNotarySignatureAsync(source, this.hashToNotarize)
);

let request = notarizedTransaction;
let response = this.retWrapper.invoke(
request,
this.retWrapper.exports.compile_notarized_transaction,
CompileNotarizedTransactionResponse
);
let compiledNotarizedTransaction = response.compiledIntent;
let notarizedPayloadHash = hash(compiledNotarizedTransaction);

return new CompiledNotarizedTransaction(
this.retWrapper,
this.intentHash,
compiledNotarizedTransaction,
notarizedPayloadHash
);
}

/**
* @returns The transaction identifier (also known as the intent hash) of the transaction, encoded into hex.
*/
Expand Down
16 changes: 16 additions & 0 deletions src/models/crypto/public_key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export class EcdsaSecp256k1 extends PublicKey {
this.publicKey = Convert.Uint8Array.from(publicKey);
}

bytes(): Uint8Array {
return this.publicKey;
}

hex(): string {
return Convert.Uint8Array.toHexString(this.publicKey);
}

toString(): string {
return JSON.stringify(this.toObject());
}
Expand All @@ -65,6 +73,14 @@ export class EddsaEd25519 extends PublicKey {
this.publicKey = Convert.Uint8Array.from(publicKey);
}

bytes(): Uint8Array {
return this.publicKey;
}

hex(): string {
return Convert.Uint8Array.toHexString(this.publicKey);
}

toString(): string {
return JSON.stringify(this.toObject());
}
Expand Down
16 changes: 16 additions & 0 deletions src/models/crypto/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export class EcdsaSecp256k1 extends Signature {
this.signature = Convert.Uint8Array.from(signature);
}

bytes(): Uint8Array {
return this.signature;
}

hex(): string {
return Convert.Uint8Array.toHexString(this.signature);
}

toString(): string {
return JSON.stringify(this.toObject());
}
Expand All @@ -65,6 +73,14 @@ export class EddsaEd25519 extends Signature {
this.signature = Convert.Uint8Array.from(signature);
}

bytes(): Uint8Array {
return this.signature;
}

hex(): string {
return Convert.Uint8Array.toHexString(this.signature);
}

toString(): string {
return JSON.stringify(this.toObject());
}
Expand Down
9 changes: 5 additions & 4 deletions src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ export namespace NetworkId {
export const Kisharnet: number = 0x0c;
export const RCnetV1: number = Kisharnet;
export const Ansharnet: number = 0x0d;
// To save confusing anyone just yet - we will uncomment this line when RCnetV2 is ready for release
// export const RCnetV2: number = Ansharnet;
export const RCnetV2: number = Ansharnet;
export const Gilganet: number = 0x20;
export const Enkinet: number = 0x21;
export const Hammunet: number = 0x22;
export const Nergalnet: number = 0x23;
export const Mardunet: number = 0x24;
export const LocalNet: number = 0xf0;
export const InternalTestNet: number = 0xf1;
export const Localnet: number = 0xf0;
export const LocalNet: number = Localnet; // Legacy alias - kept in for backwards compatibility
export const InternalTestnet: number = 0xf1;
export const InternalTestNet: number = InternalTestnet; // Legacy alias - kept in for backwards compatibility
export const Simulator: number = 0xf2;
}
Loading

0 comments on commit d78e427

Please sign in to comment.