Skip to content

Commit

Permalink
feat: support legacy cold wallets (#847)
Browse files Browse the repository at this point in the history
* import legacy cold wallets

* fix base58 address factory, pass legacy address

* handle sender legacy address

* add `@mainsail/crypto-legacy-support`

* derive create legacy sender address

* update tests

* style: resolve style guide violations

* refactor base58 address package

* add dep

* test legacy cold wallet transactions

* fix lint
  • Loading branch information
oXtxNt9U authored Feb 5, 2025
1 parent 0d857ee commit ee37abc
Show file tree
Hide file tree
Showing 69 changed files with 1,127 additions and 139 deletions.
1 change: 1 addition & 0 deletions packages/configuration-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@mainsail/container": "workspace:*",
"@mainsail/contracts": "workspace:*",
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-block": "workspace:*",
"@mainsail/crypto-commit": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Container } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";
import { ServiceProvider as CoreCryptoAddressBase58 } from "@mainsail/crypto-address-base58";
import { ServiceProvider as CoreCryptoAddressKeccak256 } from "@mainsail/crypto-address-keccak256";
import { ServiceProvider as CoreCryptoBlock } from "@mainsail/crypto-block";
import { ServiceProvider as CryptoCommit } from "@mainsail/crypto-commit";
Expand Down Expand Up @@ -65,6 +66,7 @@ export const makeApplication = async (configurationPath: string, options: Record
await app.resolve(CoreCryptoHashBcrypto).register();
await app.resolve(CoreCryptoSignatureEcdsa).register();
await app.resolve(CoreCryptoKeyPairEcdsa).register();
await app.resolve(CoreCryptoAddressBase58).register();
await app.resolve(CoreCryptoAddressKeccak256).register();
await app.resolve(CryptoMessages).register();
await app.resolve(CryptoCommit).register();
Expand Down Expand Up @@ -110,5 +112,7 @@ export const makeApplication = async (configurationPath: string, options: Record
app.bind(InternalIdentifiers.Generator.Wallet).to(WalletGenerator);
app.bind(InternalIdentifiers.Generator.Peers).to(PeersGenerator);

app.unbind(Identifiers.Cryptography.Legacy.Identity.AddressFactory);

return app;
};
1 change: 1 addition & 0 deletions packages/contracts/source/contracts/crypto/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface TransactionData {
network: number;

senderAddress: string;
senderLegacyAddress?: string;
senderPublicKey: string;
recipientAddress?: string;

Expand Down
11 changes: 10 additions & 1 deletion packages/contracts/source/contracts/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export interface Instance extends CommitHandler {
view(viewContext: TransactionViewContext): Promise<ViewResult>;
initializeGenesis(commit: GenesisInfo): Promise<void>;
getAccountInfo(address: string): Promise<AccountInfo>;
getAccountInfoExtended(address: string): Promise<AccountInfoExtended>;
getAccountInfoExtended(address: string, legacyAddress?: string): Promise<AccountInfoExtended>;
importAccountInfo(info: AccountInfoExtended): Promise<void>;
importLegacyColdWallet(wallet: LegacyColdWallet): Promise<void>;
getAccounts(offset: bigint, limit: bigint): Promise<GetAccountsResult>;
getReceipts(offset: bigint, limit: bigint): Promise<GetReceiptsResult>;
getReceipt(height: number, txHash: string): Promise<GetReceiptResult>;
Expand Down Expand Up @@ -62,6 +63,12 @@ export interface AccountInfoExtended extends AccountInfo {
readonly legacyAttributes: LegacyAttributes;
}

export interface LegacyColdWallet {
readonly address: string;
readonly balance: bigint;
readonly legacyAttributes: LegacyAttributes;
}

export interface LegacyAttributes {
readonly secondPublicKey?: string;
readonly multiSignature?: LegacyMultiSignatureAttribute;
Expand Down Expand Up @@ -95,6 +102,7 @@ export interface PrepareNextCommitContext {

export interface PreverifyTransactionContext {
readonly caller: string;
readonly legacyAddress?: string;
/** Omit recipient when deploying a contract */
readonly recipient?: string;
readonly gasLimit: bigint;
Expand All @@ -110,6 +118,7 @@ export interface PreverifyTransactionContext {

export interface TransactionContext {
readonly caller: string;
readonly legacyAddress?: string;
/** Omit recipient when deploying a contract */
readonly recipient?: string;
readonly gasLimit: bigint;
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/source/contracts/state/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Wallet {
decreaseNonce(): void;

// legacy
getLegacyAddress(): string | undefined;
hasLegacySecondPublicKey(): boolean;
legacySecondPublicKey(): string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface SenderMempool {
reAddTransactions(): Promise<Transaction[]>;
}

export type SenderMempoolFactory = (address: string) => Promise<SenderMempool>;
export type SenderMempoolFactory = (address: string, legacyAddress?: string) => Promise<SenderMempool>;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Transaction } from "../crypto/transactions.js";

export interface SenderState {
configure(address: string): Promise<SenderState>;
configure(address: string, legacyAddress?: string): Promise<SenderState>;
reset(): Promise<void>;
apply(transaction: Transaction): Promise<void>;
revert(transaction: Transaction): void;
Expand Down
7 changes: 7 additions & 0 deletions packages/contracts/source/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ export const Identifiers = {
Factory: Symbol("Crypto<Identity.Wif.Factory>"),
},
},
Legacy: {
Identity: {
AddressFactory: Symbol("Crypto<Legacy.Identity.Address.Factory>"),
AddressSerializer: Symbol("Crypto<Legacy.Identity.Address.Serializer>"),
AddressSize: Symbol("Crypto<Legacy.Identity.Address.Size>"),
},
},
Message: {
Deserializer: Symbol("Crypto<Message.Deserializer>"),
Factory: Symbol("Crypto<Message.Factory>"),
Expand Down
3 changes: 3 additions & 0 deletions packages/core/bin/config/testnet/core/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
{
"package": "@mainsail/crypto-consensus-bls12-381"
},
{
"package": "@mainsail/crypto-address-base58"
},
{
"package": "@mainsail/crypto-address-keccak256"
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@mainsail/consensus-storage": "workspace:*",
"@mainsail/container": "workspace:*",
"@mainsail/contracts": "workspace:*",
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-block": "workspace:*",
"@mainsail/crypto-commit": "workspace:*",
Expand Down
13 changes: 3 additions & 10 deletions packages/crypto-address-base58/source/address.factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@ describe<{ app: Application }>("AddressFactory", ({ assert, beforeEach, it }) =>
beforeEach(async (context) => {
context.app = new Application(new Container());
context.app.bind(Identifiers.Cryptography.Configuration).to(Configuration).inSingletonScope();
context.app.get<Contracts.Crypto.Configuration>(Identifiers.Cryptography.Configuration).setConfig({
milestones: [
// @ts-ignore
{
address: {
base58: 30,
},
},
],
});
context.app
.get<Contracts.Crypto.Configuration>(Identifiers.Cryptography.Configuration)
.set("network.pubKeyHash", 30);

await context.app.resolve(CoreValidation).register();
});
Expand Down
6 changes: 3 additions & 3 deletions packages/crypto-address-base58/source/address.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class AddressFactory implements Contracts.Crypto.AddressFactory {
const buffer: Buffer = RIPEMD160.digest(Buffer.from(publicKey, "hex"));
const payload: Buffer = Buffer.alloc(21);

payload.writeUInt8(this.configuration.getMilestone().address.base58, 0);
payload.writeUInt8(this.configuration.get("network.pubKeyHash"), 0);
buffer.copy(payload, 1);

return this.#encodeCheck(payload);
Expand All @@ -49,7 +49,7 @@ export class AddressFactory implements Contracts.Crypto.AddressFactory {
public async toBuffer(address: string): Promise<Buffer> {
const result: Buffer = this.#decodeCheck(address);

const pubKeyHash = this.configuration.getMilestone().address.base58;
const pubKeyHash = this.configuration.get("network.pubKeyHash");

if (result[0] !== pubKeyHash) {
throw new Error(`Expected address network byte ${pubKeyHash}, but got ${result[0]}.`);
Expand All @@ -60,7 +60,7 @@ export class AddressFactory implements Contracts.Crypto.AddressFactory {

public async validate(address: string): Promise<boolean> {
try {
return this.#decodeCheck(address)[0] === this.configuration.getMilestone().address.base58;
return this.#decodeCheck(address)[0] === this.configuration.get("network.pubKeyHash");
} catch {
return false;
}
Expand Down
10 changes: 7 additions & 3 deletions packages/crypto-address-base58/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import { AddressFactory } from "./address.factory.js";
import { schemas } from "./schemas.js";
import { AddressSerializer } from "./serializer.js";

export * from "./address.factory.js";
export * from "./schemas.js";

export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
this.app.bind(Identifiers.Cryptography.Identity.Address.Size).toConstantValue(21);
this.app.bind(Identifiers.Cryptography.Legacy.Identity.AddressSize).toConstantValue(21);

this.app.bind(Identifiers.Cryptography.Identity.Address.Factory).to(AddressFactory).inSingletonScope();
this.app.bind(Identifiers.Cryptography.Identity.Address.Serializer).to(AddressSerializer).inSingletonScope();
this.app.bind(Identifiers.Cryptography.Legacy.Identity.AddressFactory).to(AddressFactory).inSingletonScope();
this.app
.bind(Identifiers.Cryptography.Legacy.Identity.AddressSerializer)
.to(AddressSerializer)
.inSingletonScope();

this.#registerSchemas();
}
Expand Down
20 changes: 10 additions & 10 deletions packages/crypto-address-base58/source/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ describe<{
});

it("address - should be ok", ({ validator }) => {
assert.undefined(validator.validate("address", "a".repeat(length)).error);
assert.undefined(validator.validate("legacyAddress", "a".repeat(length)).error);

const validChars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

for (const char of validChars) {
assert.undefined(validator.validate("address", char.repeat(length)).error);
assert.undefined(validator.validate("legacyAddress", char.repeat(length)).error);
}
});

Expand All @@ -60,24 +60,24 @@ describe<{

assert.undefined(
context.validator.validate(
"address",
"legacyAddress",
await context.sandbox.app.resolve(AddressFactory).fromMnemonic(generateMnemonic(256)),
).error,
);
});

it("address - should not be ok", ({ validator }) => {
assert.defined(validator.validate("address", "a".repeat(length - 1)).error);
assert.defined(validator.validate("address", "a".repeat(length + 1)).error);
assert.defined(validator.validate("address", 123).error);
assert.defined(validator.validate("address", null).error);
assert.defined(validator.validate("address").error);
assert.defined(validator.validate("address", {}).error);
assert.defined(validator.validate("legacyAddress", "a".repeat(length - 1)).error);
assert.defined(validator.validate("legacyAddress", "a".repeat(length + 1)).error);
assert.defined(validator.validate("legacyAddress", 123).error);
assert.defined(validator.validate("legacyAddress", null).error);
assert.defined(validator.validate("legacyAddress").error);
assert.defined(validator.validate("legacyAddress", {}).error);

const invalidChars = "!#$%&'|+/";

for (const char of invalidChars) {
assert.defined(validator.validate("address", char.repeat(length)).error);
assert.defined(validator.validate("legacyAddress", char.repeat(length)).error);
}
});
});
4 changes: 2 additions & 2 deletions packages/crypto-address-base58/source/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const schemas = {
address: {
$id: "address",
legacyAddress: {
$id: "legacyAddress",
allOf: [
{
maxLength: 34,
Expand Down
1 change: 1 addition & 0 deletions packages/crypto-block/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"ajv": "8.12.0"
},
"devDependencies": {
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-config": "workspace:*",
"@mainsail/crypto-consensus-bls12-381": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/crypto-block/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Contracts, Identifiers } from "@mainsail/contracts";
import { ServiceProvider as CoreCryptoAddressKeccak256 } from "@mainsail/crypto-address-keccak256";
import { ServiceProvider as CoreCryptoAddressBase58 } from "@mainsail/crypto-address-base58";
import { ServiceProvider as CoreCryptoConfig } from "@mainsail/crypto-config";
import { ServiceProvider as CoreCryptoConsensus } from "@mainsail/crypto-consensus-bls12-381";
import { ServiceProvider as CoreCryptoHashBcrypto } from "@mainsail/crypto-hash-bcrypto";
Expand Down Expand Up @@ -59,6 +60,7 @@ export const prepareSandbox = async (context) => {
await context.sandbox.app.resolve(CoreCryptoSignatureEcdsa).register();
await context.sandbox.app.resolve(CoreCryptoConsensus).register();
await context.sandbox.app.resolve(CoreCryptoKeyPairEcdsa).register();
await context.sandbox.app.resolve(CoreCryptoAddressBase58).register();
await context.sandbox.app.resolve(CoreCryptoAddressKeccak256).register();
await context.sandbox.app.resolve(CoreCryptoWif).register();
await context.sandbox.app.resolve(CoreCryptoTransaction).register();
Expand Down
1 change: 1 addition & 0 deletions packages/crypto-commit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@mainsail/utils": "workspace:*"
},
"devDependencies": {
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-block": "workspace:*",
"@mainsail/crypto-config": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/crypto-commit/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Identifiers } from "@mainsail/contracts";
import { ServiceProvider as CoreCryptoAddressKeccak256 } from "@mainsail/crypto-address-keccak256";
import { ServiceProvider as CoreCryptoAddressBase58 } from "@mainsail/crypto-address-base58";
import { ServiceProvider as CoreCryptoBlock } from "@mainsail/crypto-block";
import { ServiceProvider as CoreCryptoConfig } from "@mainsail/crypto-config";
import { ServiceProvider as CoreCryptoConsensus } from "@mainsail/crypto-consensus-bls12-381";
Expand Down Expand Up @@ -42,6 +43,7 @@ export const prepareSandbox = async (context) => {
await context.sandbox.app.resolve(CoreCryptoConsensus).register();
await context.sandbox.app.resolve(CoreCryptoKeyPairSchnorr).register();
await context.sandbox.app.resolve(CoreCryptoAddressKeccak256).register();
await context.sandbox.app.resolve(CoreCryptoAddressBase58).register();
await context.sandbox.app.resolve(CoreCryptoWif).register();
await context.sandbox.app.resolve(CoreCryptoTransaction).register();
await context.sandbox.app.resolve(CoreCryptoBlock).register();
Expand Down
1 change: 1 addition & 0 deletions packages/crypto-messages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"ajv": "8.12.0"
},
"devDependencies": {
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-block": "workspace:*",
"@mainsail/crypto-config": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions packages/crypto-messages/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Contracts, Identifiers } from "@mainsail/contracts";
import { ServiceProvider as CoreCryptoAddressKeccak256 } from "@mainsail/crypto-address-keccak256";
import { ServiceProvider as CoreCryptoAddressBase58 } from "@mainsail/crypto-address-base58";
import { ServiceProvider as CryptoBlock } from "@mainsail/crypto-block";
import { ServiceProvider as CoreCryptoConfig } from "@mainsail/crypto-config";
import { ServiceProvider as CoreConsensusBls12381 } from "@mainsail/crypto-consensus-bls12-381";
Expand Down Expand Up @@ -37,6 +38,7 @@ export const prepareSandbox = async (context: { sandbox?: Sandbox }) => {
await context.sandbox.app.resolve(CoreCryptoSignatureEcdsa).register();
await context.sandbox.app.resolve(CoreCryptoKeyPairEcdsa).register();
await context.sandbox.app.resolve(CoreCryptoAddressKeccak256).register();
await context.sandbox.app.resolve(CoreCryptoAddressBase58).register();
await context.sandbox.app.resolve(CoreCryptoWif).register();
await context.sandbox.app.resolve(CoreConsensusBls12381).register();
await context.sandbox.app.resolve(CoreCryptoTransaction).register();
Expand Down
1 change: 1 addition & 0 deletions packages/crypto-transaction-evm-call/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@mainsail/utils": "workspace:*"
},
"devDependencies": {
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-config": "workspace:*",
"@mainsail/crypto-key-pair-ecdsa": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler {

const { evmSpec } = this.configuration.getMilestone();

const { senderAddress } = transaction.data;
const { senderAddress, senderLegacyAddress } = transaction.data;

try {
const { instance, blockContext } = context.evm;
Expand All @@ -44,6 +44,7 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler {
data: Buffer.from(transaction.data.data, "hex"),
gasLimit: BigInt(transaction.data.gasLimit),
gasPrice: BigInt(transaction.data.gasPrice),
legacyAddress: senderLegacyAddress,
nonce: transaction.data.nonce.toBigInt(),
recipient: transaction.data.recipientAddress,
sequence: transaction.data.sequence,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Contracts, Identifiers } from "@mainsail/contracts";
import { schemas as addressSchemas } from "@mainsail/crypto-address-keccak256";
import { schemas as base58AddressSchemas } from "@mainsail/crypto-address-base58";
import { Configuration } from "@mainsail/crypto-config";
import { schemas as kayParSchemas } from "@mainsail/crypto-key-pair-ecdsa";
import { makeFormats, makeKeywords, schemas as transactionSchemas } from "@mainsail/crypto-transaction";
Expand Down Expand Up @@ -43,6 +44,7 @@ describe<{
...transactionSchemas,
...kayParSchemas,
...addressSchemas,
...base58AddressSchemas,
})) {
context.validator.addSchema(schema);
}
Expand Down
1 change: 1 addition & 0 deletions packages/crypto-transaction/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"ethers": "6.11.0"
},
"devDependencies": {
"@mainsail/crypto-address-base58": "workspace:*",
"@mainsail/crypto-address-keccak256": "workspace:*",
"@mainsail/crypto-config": "workspace:*",
"@mainsail/crypto-key-pair-ecdsa": "workspace:*",
Expand Down
Loading

0 comments on commit ee37abc

Please sign in to comment.