静态代理设计模式封装区块链签名jar包

本文介绍了一种在Java中实现区块链交易签名的方法。通过模仿现有的JavaScript签名实现,并采用静态代理设计模式,创建了一个名为Web3jSignerProvider的类,用于在发送交易前自动进行签名。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题开始:

    在区块链钱包的开发中发现交易方法需要先签名才能进行交易(eth_sendTransaction)否则就需要对账户进行解锁.需要开发一个签名提供方法.通过查找资料发现有比较好的JS签名实现方法,但没有很好的java实现,所以想模仿其思路实现一个java版的.

开始设计:

该方法的类图

    通过此图可以清晰的看出采用了静态代理的设计模式,新建类Web3jSignerProvider来重写了Web3jService接口的方法,实现了对Service类(既图中的HttpService类)中方法的增强.

    而在Web3jSignerProvider中又调用了接口Signer的方法来实现对交易的签名,其中签名的具体实现在PricateKeySigner中实现.

    最终实现了,使用者只要初始化Web3jSignerProvider对象就可以在调用正常的交易方法时在底层自动对其交易进行签名.

P.s.虽然这样对单一项目来说反而代码量增加 不如直接copy签名实现方法的代码 在项目中签名再交易 但之所以这样做的原因是:代码泛用性更强,既面对对象性更强.其他项目只要引用了该jar包既在grandle中配置一下就可以做到实现该方法而不用考虑其他东西.

开始实现:

首先实现签名的方法,先上代码:

org.ethereum.core.Transaction transactionObj = new org.ethereum.core.Transaction(nonceByte, gasPriceByte, gasLimitByte, receiveAddress, valueByte, dataByte);
transactionObj.sign(ECKey.fromPrivate(privateKey));

return "0x" + Hex.toHexString(transactionObj.getEncoded());

签名的核心实现,既重构一个Transaction对象并调用其sign()方法对其签名,并返回签名后的交易地址,这里有一个小'' 有签名方法的Transaction对象和在交易时使用的Transaction不是一个.所以需要将传入的Transaction的值全取出来并重构.

/**
 * A transaction (formally, T) is a single cryptographically
 * signed instruction sent by an actor external to Ethereum.
 * An external actor can be a person (via a mobile device or desktop computer)
 * or could be from a piece of automated software running on a server.
 * There are two types of transactions: those which result in message calls
 * and those which result in the creation of new contracts.
 */
public class Transaction //可以签名的Transaction对象,在注释中也说明了是一个单独加密的.可以对其进行签名指令.

想要调用签名方法需要对其初始化

public PrivateKeySigner(BigInteger privateKey) throws Exception {
    if (privateKey == null) {
        throw new Exception("privateKey is null");
    }
    this.privateKey = privateKey;
}

传入私钥进行签名,并进行入参校验

接下来完善签名方法,参数除了privateKey都从Transaction中取到,并进行入参校验与格式转换.又有一个小坑就是16进制数必须是偶数位,如果奇数位则在其前面补一个0.

    @Override
    public String sign(Transaction transaction) throws Exception {
        String value;
        String nonce;
        String gasPrice;
        String gasLimit;
        String data;
        String toAddress;

        if (transaction.getValue().length() % 2 == 0) {
            value = transaction.getValue().replace("0x", "");
        } else {
            value = transaction.getValue().replace("0x", "0");
        }
        
        if (transaction.getNonce().length() % 2 == 0) {
            nonce = transaction.getNonce().replace("0x","");
        } else {
            nonce = transaction.getNonce().replace("0x","0");
        }
        
        if (transaction.getGasPrice().length() % 2 == 0) {
            gasPrice = transaction.getGasPrice().replace("0x","");
        } else {
            gasPrice = transaction.getGasPrice().replace("0x","0");
        }
        
        if (transaction.getGas().length() % 2 == 0) {
            gasLimit = transaction.getGas().replace("0x","");
        } else {
            gasLimit = transaction.getGas().replace("0x","0");
        }

        if (transaction.getData() == null) {
            data = "";
        } else if (transaction.getData().length() % 2 == 0) {
            data = transaction.getData().replace("0x","");
        } else {
            data = transaction.getData().replace("0x","0");
        }

        if (transaction.getTo() == null) {
            throw new Exception("receiveAddress is null");
        } else if (transaction.getTo().length() % 2 == 0) {
            toAddress = transaction.getTo().replace("0x","");
        } else {
            toAddress = transaction.getTo().replace("0x","0");
        }

        if (gasLimit == null) {
            gasLimit = "21000"; //min
        }
        
        if (gasPrice == null) {
            gasPrice = "5000000000"; //min
        }
        
        if (value == null) {
            throw new Exception("Value is null");
        }

        byte[] receiveAddress = Hex.decode(toAddress.replace("0x", ""));
        byte[] gasPriceByte = Hex.decode(gasPrice.replace("0x", ""));
        byte[] gasLimitByte = Hex.decode(gasLimit.replace("0x", ""));
        byte[] valueByte = Hex.decode(value);
        byte[] dataByte = Hex.decode(data.replace("0x", ""));
        byte[] nonceByte = Hex.decode(nonce.replace("0x", ""));

        org.ethereum.core.Transaction transactionObj = new org.ethereum.core.Transaction(nonceByte, gasPriceByte, gasLimitByte, receiveAddress, valueByte, dataByte);
        transactionObj.sign(ECKey.fromPrivate(privateKey));

        return "0x" + Hex.toHexString(transactionObj.getEncoded());
    }

至此完成了签名接口的实现,接下来就是分析源码,并代理Service方法,先上接口的代码.

public interface Web3jService {
    <T extends Response> T send(
            Request request, Class<T> responseType) throws IOException;

    <T extends Response> CompletableFuture<T> sendAsync(
            Request request, Class<T> responseType);
}

为什么重写这个类呢?因为首先通过JS的实现方法获取了目标,之后通过断点寻找发现

@Override
    public <T extends Response> T send(
            Request request, Class<T> responseType) throws IOException {
        String payload = objectMapper.writeValueAsString(request);

        try (InputStream result = performIO(payload)) {
            if (result != null) {
                return objectMapper.readValue(result, responseType);
            } else {
                return null;
            }
        }
    }

这里Service的实现方法里,有传入request的方法 而request里包含了所有的交易信息 我只要在这里抓取我想要的方法,并重构request对象(交易信息)就可以实现签名,而且这个HttpService可以直接通过传入地址参数构造出来,我只要让使用者通过new我创建的代理对象而不是之前的HttpService(继承了Service类)就可以达到覆盖原方法的作用.

接下来就是要对其进行重写:

public class Web3jSignerProvider implements org.web3j.protocol.Web3jService{

    private HttpService service;
    private Signer signer;

    public Web3jSignerProvider(String url, Signer signer) throws Exception {
        if (url == null) {
            throw new Exception("url is null");
        }
        
        if (signer == null) {
            throw new Exception("Please initialization signer");
        }
        
        this.service = new HttpService(url);
        this.signer = signer;
    }

    @Override
    public <T extends Response> T send(Request request, Class<T> responseType) throws IOException {
        if (request.getMethod().equals("eth_sendTransaction")) {
            Transaction transactoin = (Transaction) request.getParams().get(0);
            try {
                //Reconstruction Requset For Find Nonce
                Request requestCount = new Request<>(
                        "eth_getTransactionCount",
                        Arrays.asList(transactoin.getFrom(), DefaultBlockParameterName.LATEST.getValue()),
                        service,
                        EthGetTransactionCount.class);

                EthGetTransactionCount count = (EthGetTransactionCount) requestCount.send();
                BigInteger nonce = count.getTransactionCount();

                //Reconstruction Transaction For Signer
                Transaction signerTransaction = new Transaction(
                        transactoin.getFrom(),
                        nonce,
                        new BigInteger(transactoin.getGasPrice().replace("0x",""), 16),
                        new BigInteger(transactoin.getGas().replace("0x",""), 16),
                        transactoin.getTo(),
                        new BigInteger(transactoin.getValue().replace("0x",""), 16),
                        transactoin.getData()
                );

                //Signer
                String newTrans = signer.sign(signerTransaction);

                //Reconstruction Requset For Send
                Request requestRaw = new Request<>(
                        "eth_sendRawTransaction",
                        Collections.singletonList(newTrans),
                        service,
                        EthSendTransaction.class);

                //sendRawTransaction
                return (T) requestRaw.send();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }  else {
           return service.send(request, responseType);
        }
    }

    @Override
    public <T extends Response> CompletableFuture<T> sendAsync(Request request, Class<T> responseType) {
        if (request.getMethod().equals("eth_sendTransaction")) {
            return Async.run(() -> send(request, responseType));
        } else {
            return service.sendAsync(request, responseType);
        }
    }
}

因为要签名所以构造方法新加了Signer参数,之后就研究怎样可以重写传递的参数.

public Request(String method, 	交易或查询调用的方法
	    List<S> params,	交易详细信息
            Web3jService web3jService, 
            Class<T> type)

我只需要抓取正常交易的方法,之后调用签名方法替换掉签名后的params的内容和只能使用签名后的交易信息的签名方法就好了.这些信息的发现都可以在Debug中获取.

打包发布:

先修改markdown文档


之后完善grandle配置

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "http://...") {
                authentication(userName: "...", password: "...")
            }

            snapshotRepository(url: "http://.../") {
                authentication(userName: "...", password: "...")
            }

            pom.project {
                name='cn.kuick.blockchain:web3j-sign-provider'
                packaging='jar'
                description='A simple web3 standard provider that signs sendTransaction payloads.'
            }
        }
    }
}

artifacts {
    archives jar
}

通过在grandle中添加这两个配置,并输入gradle uploadArchives命令来打包上传.

至此这个小模块就实现完毕 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值