Skip to content

Commit 02f1356

Browse files
Merge pull request #985 from ProvableHQ/offline-tx-signing
Fix Create Leo App's offline public transaction signing template
2 parents 055820a + fb5b0af commit 02f1356

File tree

4 files changed

+77
-63
lines changed

4 files changed

+77
-63
lines changed

create-leo-app/template-offline-public-transaction-ts/README.md

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Offline Transaction Builder
22

33
## 1. Overview
4-
### 1.1 Proving Keys for Zero Knowledge Function Execution
5-
To achieve zero knowledge execution, all Aleo functions require a `ProvingKey` and `VerifyingKey` in order to build a
6-
zero knowledge ZkSnark proof of execution. If a user does not possess these keys for a function, they are normally
4+
### 1.1 Proving Keys for Zero-Knowledge Function Execution
5+
To achieve zero-knowledge execution, all Aleo functions require a `ProvingKey` and `VerifyingKey` in order to build a
6+
zero-knowledge zk-SNARK proof of execution. If a user does not possess these keys for a function, they are normally
77
downloaded from the internet when the function is called.
88

99
### 1.2 Key Providers
@@ -21,31 +21,27 @@ the internet for it. This provides a way to build Aleo execution transactions wi
2121
This pathway is suitable for use-cases such as hardware wallets or air-gapped machines used
2222
for building secure transactions.
2323

24-
### 1.4 Assumptions
24+
### 1.4 Transaction Types
2525

26-
The key material in this example is assumed to be pre-downloaded onto the machine performing the
27-
construction of the offline transaction.
26+
Several types of transactions can be built and executed using this template:
27+
28+
`bond_public`
29+
`unbond_public`
30+
`claim_unbond_public`
2831

2932
## 2. Usage
3033

31-
### 2.1 Pre-Download the Keys
32-
First run this command online to download the key material to disk:
34+
`npm run build`
3335

34-
`npm start`
36+
`npm run dev`
3537

3638
Once this command is run, all proving keys for the `transfer_public`, `bond_public`, `unbond_public`, and
37-
`claim_unbond_public` functions will be downloaded to the `./keys` folder. The machine can then be disconnected from
39+
`claim_unbond_public` functions will be downloaded to the `dist/keys` folder. The machine can then be disconnected from
3840
the internet and the `OfflineKeyProvider` will search this directory for the function proving keys when building the
3941
transaction instead of connecting to the internet. Alternatively you can skip the online step entirely by adding the proving key creating this directory manually and
4042
adding the key material yourself.
4143

42-
### 2.2 Build the Transaction Offline
43-
44-
Once the key material is downloaded, turn off your internet connection and run the following command:
45-
46-
`npm start`
47-
48-
You should see the transactions being built and the resulting transaction IDs printed to the console.
44+
Once the keys are downloaded to your local machine, the offline transactions will be built without requiring an internet connection.
4945

5046
## 3. Notes
5147

create-leo-app/template-offline-public-transaction-ts/src/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async function preDownloadTransferKeys() {
2828
const keysDirPath = path.join(__dirname, "keys");
2929
await fsPromises.mkdir(keysDirPath, { recursive: true });
3030

31-
for (const keyData of [CREDITS_PROGRAM_KEYS.transfer_public, CREDITS_PROGRAM_KEYS.fee_public]) {
31+
for (const keyData of [CREDITS_PROGRAM_KEYS.transfer_public, CREDITS_PROGRAM_KEYS.fee_public, CREDITS_PROGRAM_KEYS.transfer_public_as_signer]) {
3232
try {
3333
keyPaths[keyData.locator] = await downloadAndSaveKey(keyData, keysDirPath);
3434
} catch (error) {

create-leo-app/template-offline-public-transaction-ts/src/index.ts

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ async function buildTransferPublicTxOffline(recipientAddress: Address, amount: n
1515
// Create the proving keys from the key bytes on the offline machine
1616
console.log("Creating proving keys from local key files");
1717
const feePublicKeyBytes = await getLocalKey(<string>keyPaths[CREDITS_PROGRAM_KEYS.fee_public.locator]);
18-
const transferPublicAsSignerKeyBytes = await getLocalKey(<string>keyPaths[CREDITS_PROGRAM_KEYS.transfer_public_as_signer.locator]);
1918
const feePublicProvingKey = ProvingKey.fromBytes(feePublicKeyBytes);
20-
const transferPublicProvingKey = ProvingKey.fromBytes(transferPublicAsSignerKeyBytes);
21-
19+
const transferPublicProvingKey = ProvingKey.fromBytes(
20+
await getLocalKey(<string>keyPaths[CREDITS_PROGRAM_KEYS.transfer_public.locator])
21+
);
22+
2223
// Create an offline key provider
2324
console.log("Creating offline key provider");
2425
const offlineKeyProvider = new OfflineKeyProvider();
@@ -27,10 +28,25 @@ async function buildTransferPublicTxOffline(recipientAddress: Address, amount: n
2728
// keys into the key manager.
2829
console.log("Inserting proving keys into key provider");
2930
offlineKeyProvider.insertFeePublicKeys(feePublicProvingKey);
31+
32+
try {
3033
offlineKeyProvider.insertTransferPublicKeys(transferPublicProvingKey);
34+
console.log("Successfully inserted proving key");
35+
} catch (err) {
36+
console.error("Failed to insert proving key:", err);
37+
}
38+
3139

3240
// Create an offline query to complete the inclusion proof
33-
const offlineQuery = new OfflineQuery(latestStateRoot);
41+
let offlineQuery: OfflineQuery;
42+
const blockHeight = 0;
43+
// TODO this is a placeholder block height for now, which offlineQuery now requires
44+
try {
45+
const offlineQuery = new OfflineQuery(blockHeight, latestStateRoot);
46+
console.log("Successfully created OfflineQuery", offlineQuery);
47+
} catch (err) {
48+
console.error("Failed to create OfflineQuery:", err);
49+
}
3450

3551
// Insert the key provider into the program manager
3652
programManager.setKeyProvider(offlineKeyProvider);
@@ -47,7 +63,13 @@ async function buildTransferPublicTxOffline(recipientAddress: Address, amount: n
4763
}
4864

4965
/// Build bonding and unbonding transactions without connection to the internet
50-
async function buildBondingTxOffline(stakerAddress: Address, validatorAddress: Address, withdrawalAddress: Address, amount: number, latestStateRoot: string, keyPaths: {}): Promise<Transaction[]> {
66+
async function buildBondingTxOffline(
67+
validatorAddress: Address,
68+
withdrawalAddress: Address,
69+
amount: number,
70+
latestStateRoot: string,
71+
keyPaths: {}
72+
): Promise<Transaction[]> {
5173
// Create an offline program manager
5274
const programManager = new ProgramManager();
5375

@@ -70,8 +92,7 @@ async function buildBondingTxOffline(stakerAddress: Address, validatorAddress:
7092
console.log("Creating offline key provider");
7193
const offlineKeyProvider = new OfflineKeyProvider();
7294

73-
// Insert the proving keys into the offline key provider. The key provider will automatically insert the verifying
74-
// keys into the key manager.
95+
// Insert the proving keys into the offline key provider
7596
console.log("Inserting proving keys into key provider");
7697
offlineKeyProvider.insertFeePublicKeys(feePublicProvingKey);
7798
offlineKeyProvider.insertBondPublicKeys(bondPublicProvingKey);
@@ -83,49 +104,48 @@ async function buildBondingTxOffline(stakerAddress: Address, validatorAddress:
83104

84105
// Build the bonding transactions offline
85106
console.log("Building a bond_public execution transaction offline");
86-
const bondPublicOptions = {
87-
executionParams: {
88-
keySearchParams: OfflineSearchParams.bondPublicKeyParams()
89-
},
90-
offlineParams: {
91-
offlineQuery: new OfflineQuery(latestStateRoot)
92-
}
107+
108+
if (!latestStateRoot) {
109+
throw new Error("latestStateRoot is undefined");
93110
}
94111

112+
const bondPublicOptions = {
113+
keySearchParams: OfflineSearchParams.bondPublicKeyParams(),
114+
offlineQuery: new OfflineQuery(0, latestStateRoot)
115+
};
116+
117+
95118
const bondTx = <Transaction>await programManager.buildBondPublicTransaction(
96-
stakerAddress.to_string(),
97119
validatorAddress.to_string(),
98120
withdrawalAddress.to_string(),
99121
amount,
100-
bondPublicOptions,
101-
)
122+
bondPublicOptions
123+
);
124+
102125
console.log("\nbond_public transaction built!\n");
103126

104-
console.log("Building an unbond_public execution transaction offline")
105127
const unbondPublicOptions = {
106-
executionParams: {
107-
keySearchParams: OfflineSearchParams.unbondPublicKeyParams()
108-
},
109-
offlineParams: {
110-
offlineQuery: new OfflineQuery(latestStateRoot)
111-
}
112-
}
128+
keySearchParams: OfflineSearchParams.unbondPublicKeyParams(),
129+
offlineQuery: new OfflineQuery(0, latestStateRoot)
130+
};
113131

114-
const unBondTx = <Transaction>await programManager.buildUnbondPublicTransaction(stakerAddress.to_string(), amount, unbondPublicOptions);
132+
const unBondTx = <Transaction>await programManager.buildUnbondPublicTransaction(
133+
stakerAddress.to_string(),
134+
amount,
135+
unbondPublicOptions
136+
);
115137
console.log("\nunbond_public transaction built!\n");
116138

117-
console.log("Building a claim_unbond_public transaction offline")
118-
// Build the claim unbonding transaction offline
139+
console.log("Building a claim_unbond_public transaction offline");
119140
const claimUnbondPublicOptions = {
120-
executionParams: {
121-
keySearchParams: OfflineSearchParams.claimUnbondPublicKeyParams()
122-
},
123-
offlineParams: {
124-
offlineQuery: new OfflineQuery(latestStateRoot)
125-
}
126-
}
141+
keySearchParams: OfflineSearchParams.claimUnbondPublicKeyParams(),
142+
offlineQuery: new OfflineQuery(0, latestStateRoot)
143+
};
127144

128-
const claimUnbondTx = <Transaction>await programManager.buildClaimUnbondPublicTransaction(stakerAddress.to_string(), claimUnbondPublicOptions);
145+
const claimUnbondTx = <Transaction>await programManager.buildClaimUnbondPublicTransaction(
146+
stakerAddress.to_string(),
147+
claimUnbondPublicOptions
148+
);
129149
console.log("\nclaim_unbond_public transaction built!\n");
130150
return [bondTx, unBondTx, claimUnbondTx];
131151
}
@@ -153,13 +173,13 @@ console.log(`\n---------------transfer_public transaction---------------\n${tran
153173
console.log(`---------------------------------------------------------`);
154174

155175
// Build bonding & unbonding transactions
156-
const bondTransactions = await buildBondingTxOffline(stakerAddress, validatorAddress, withdrawalAddress, 100, latestStateRoot, bondingKeyPaths);
176+
const bondTransactions = await buildBondingTxOffline(validatorAddress, withdrawalAddress, 100, latestStateRoot, bondingKeyPaths);
157177
console.log("Bonding transactions built offline!");
158178
console.log(`\n-----------------bond_public transaction-----------------\n${bondTransactions[0]}`);
159179
console.log(`---------------------------------------------------------`);
160180
console.log(`\n----------------unbond_public transaction:---------------\n${bondTransactions[1]}`);
161181
console.log(`---------------------------------------------------------`);
162-
console.log(`\n-----------------claim_unbond transaction:---------------\n${bondTransactions[2]}`);
182+
console.log(`\n-----------------claim_unbond_public transaction:---------------\n${bondTransactions[2]}`);
163183
console.log(`---------------------------------------------------------`);
164184
//---------------------------------------------------------
165185

@@ -168,4 +188,4 @@ console.log(`---------------------------------------------------------`);
168188
// ONLINE COMPONENT (Uncomment this part to send the transaction to the Aleo Network on an internet connected machine)
169189
// Submit the transaction to the network
170190
// const transferTxId = await networkClient.submitTransaction(transferTx);
171-
//---------------------------------------------------------
191+
//---------------------------------------------------------

sdk/src/program-manager.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,6 @@ class ProgramManager {
843843
* const result = await programManager.networkClient.submitTransaction(tx);
844844
*
845845
* @returns string
846-
* @param {string} staker_address Address of the staker who is bonding the credits
847846
* @param {string} validator_address Address of the validator to bond to, if this address is the same as the staker (i.e. the
848847
* executor of this function), it will attempt to bond the credits as a validator. Bonding as a validator currently
849848
* requires a minimum of 10,000,000 credits to bond (subject to change). If the address is specified is an existing
@@ -853,15 +852,15 @@ class ProgramManager {
853852
* @param {number} amount The amount of credits to bond
854853
* @param {Partial<ExecuteOptions>} options - Override default execution options.
855854
*/
856-
async buildBondPublicTransaction(staker_address: string, validator_address: string, withdrawal_address: string, amount: number, options: Partial<ExecuteOptions> = {}) {
855+
async buildBondPublicTransaction(validator_address: string, withdrawal_address: string, amount: number, options: Partial<ExecuteOptions> = {}) {
857856
const scaledAmount = Math.trunc(amount * 1000000);
858857

859858
const {
860859
programName = "credits.aleo",
861860
functionName = "bond_public",
862861
fee = options.fee || 0.86,
863862
privateFee = false,
864-
inputs = [staker_address, validator_address, withdrawal_address, `${scaledAmount.toString()}u64`],
863+
inputs = [validator_address, withdrawal_address, `${scaledAmount.toString()}u64`],
865864
keySearchParams = new AleoKeyProviderParams({
866865
proverUri: CREDITS_PROGRAM_KEYS.bond_public.prover,
867866
verifierUri: CREDITS_PROGRAM_KEYS.bond_public.verifier,
@@ -900,7 +899,6 @@ class ProgramManager {
900899
* const tx_id = await programManager.bondPublic("aleo1jx8s4dvjepculny4wfrzwyhs3tlyv65r58ns3g6q2gm2esh7ps8sqy9s5j", "aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px", "aleo1feya8sjy9k2zflvl2dx39pdsq5tju28elnp2ektnn588uu9ghv8s84msv9", 2000000);
901900
*
902901
* @returns string
903-
* @param {string} staker_address Address of the staker who is bonding the credits
904902
* @param {string} validator_address Address of the validator to bond to, if this address is the same as the signer (i.e. the
905903
* executor of this function), it will attempt to bond the credits as a validator. Bonding as a validator currently
906904
* requires a minimum of 1,000,000 credits to bond (subject to change). If the address is specified is an existing
@@ -910,8 +908,8 @@ class ProgramManager {
910908
* @param {number} amount The amount of credits to bond
911909
* @param {Options} options Options for the execution
912910
*/
913-
async bondPublic(staker_address: string, validator_address: string, withdrawal_address:string, amount: number, options: Partial<ExecuteOptions> = {}) {
914-
const tx = <Transaction>await this.buildBondPublicTransaction(staker_address, validator_address, withdrawal_address, amount, options);
911+
async bondPublic(validator_address: string, withdrawal_address:string, amount: number, options: Partial<ExecuteOptions> = {}) {
912+
const tx = <Transaction>await this.buildBondPublicTransaction(validator_address, withdrawal_address, amount, options);
915913
return await this.networkClient.submitTransaction(tx);
916914
}
917915

0 commit comments

Comments
 (0)