solidty的动态数组在内联汇编中,首个32字节槽位存放数组长度
//bytes转换为bytes32
function bytesToBytes32(bytes memory source) returns (bytes32 result) {
assembly {
result := mload(add(source, 32))
}
}
代码解析为:source的内存起始位偏移32个字节。mload(p) mem[p..(p+32)) 从指定地址加载32字节, 略过前面的长度, 直接强制转换为 bytes32 同时略去后面大于32的。
Solidity中的惯例
与EVM汇编不同,Solidity知道类型少于256字节,如,uint24。为了让他们更高效,大多数的数学操作仅仅是把也们当成是一个256字节的数字进行计算,高位的字节只在需要的时候才会清理,比如在写入内存前,或者在需要比较时。这意味着如果你在内联汇编中访问这样的变量,你必须要手动清除高位的无效字节。
Solidity以非常简单的方式来管理内存:内部存在一个空间内存的指针在内存位置0x40。如果你想分配内存,可以直接使用从那个位置的内存,并相应的更新指针。
Solidity中的内存数组元素,总是占用多个32字节的内存(也就是说byte[]也是这样,但是bytes和string不是这样)。多维的memory的数组是指向memory的数组。一个动态数组的长度存储在数据的第一个槽位,紧接着就是数组的元素。
固定长度的memory数组没有一个长度字段,但它们将很快增加这个字段,以让定长与变长数组间有更好的转换能力,所以请不要依赖于这点。
Solidity校验椭圆曲线加密数字签名
在网上可以找到一个教程《区块链语言Solidity校验椭圆曲线加密数字签名(附实例)》
按照他solidity的例子是可以恢复出账户地址,但我本地用geth尝试他的步骤操作,就恢复不到账户地址。
最后google了一些资料和查看geth和web3js的源代码,找到了答案。
1. web3.eth.sign最终会调用geth里的签名方法,geth会再次以这个格式:"\x19Ethereum Signed Message:\n" + len(msg) + msg,完成最后哈希。
go-ethereum源码
// signHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calulcated as
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func signHash(data []byte) []byte {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256([]byte(msg))
}
web3js源码也有这样的说明
// Unfortunately Geth client adds this line to the message as a prefix while signing
// So while finding who signed it we need to prefix this part
function signMessage(message){
initializeEthereumConnection();
if(ethWeb3.isConnected()==false){
return false;
}
var state=unlockAccount(defaultAc);
const msg = new Buffer(message);
const sig = ethWeb3.eth.sign(defaultAc, '0x' + msg.toString('hex'));
return sig;
}
function verifySignedByAc(message, sig){
initializeEthereumConnection();
if(ethWeb3.isConnected()==false){
return false;
}
initializeContract();
const res = splitSig(sig);
// Unfortunately Geth client adds this line to the message as a prefix while signing
// So while finding who signed it we need to prefix this part
const prefix = new Buffer("\x19Ethereum Signed Message:\n");
const msg = new Buffer(message);
const prefixedMsg = ethWeb3.sha3(
Buffer.concat([prefix, new Buffer(String(msg.length)), msg]).toString('utf8')
);
var strPrefixedMsg=prefixedMsg;
var finalAddress=sigContractInstance.verify.call(strPrefixedMsg, res.v, res.r, '0x'+ res.s);
return finalAddress;
}
2. 签名中r,s,v三个值,geth消息签名已经把v加上了27。所以合约程序里不需要再+27,或者灵活点,如果v是27或28,就不需要+27,如果v是0或1就+27。
go-ethereum源码部分
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
// Assemble sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data))
if err != nil {
return nil, err
}
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
return signature, nil
}
geth消息签名相关介绍:
https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
Solidity使用string[]或bytes[]作为参数或返回值需要注意
目前solidity的【外部方法】public无法使用string[]或bytes[]做传参及返回值。编译提示错误:
UnimplementedFeatureError: Nested dynamic arrays not implemented here.
但【内部方法】private就不限制。