Skip to content

Commit

Permalink
contracts: sr25519 support
Browse files Browse the repository at this point in the history
  • Loading branch information
CedarMist committed Dec 6, 2024
1 parent a4069ee commit 61c78da
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 68 deletions.
10 changes: 10 additions & 0 deletions contracts/contracts/Sapphire.sol
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ library Sapphire {
* - `4` (`Secp256k1PrehashedKeccak256`)
* - `5` (`Secp256k1PrehashedSha256`)
*
* ##### sr25519: 1,000 gas
* - `6` (`sr25519`)
*
* ##### Secp256r1: 4,000 gas
* - `7` (`Secp256r1PrehashedSha256`)
*
Expand All @@ -343,6 +346,11 @@ library Sapphire {
* byte X coordinate).
* Secret key: 48 bytes
*
* ##### sr25519
*
* Public key: 32 bytes
* Secret key: 96 bytes (64 byte secret key, 32 byte public key)
*
* #### Example
*
* ```solidity
Expand Down Expand Up @@ -401,6 +409,7 @@ library Sapphire {
* (32 bytes) as context, empty message.
* - `5` (`Secp256k1PrehashedSha256`): 3,000 gas, pre-existing hash (32
* bytes) as context, empty message.
* - `6` (`sr25519`): 1,500 gas, bytes (e.g. 'substrate) as context, arbitrary length message
* - `7` (`Secp256r1PrehashedSha256`): 9,000 gas, pre-existing hash (32
* bytes) as context, empty message.
* - `8` (`Secp384r1PrehashedSha384`): 43,200 gas, pre-existing hash (32
Expand Down Expand Up @@ -462,6 +471,7 @@ library Sapphire {
* - `3` (`Secp256k1Oasis`): 3,000 gas
* - `4` (`Secp256k1PrehashedKeccak256`): 3,000 gas
* - `5` (`Secp256k1PrehashedSha256`): 3,000 gas
* - `6` (`sr25519`): 2,000 gas
* - `7` (`Secp256r1PrehashedSha256`): 7,900 gas
* - `8` (`Secp384r1PrehashedSha384`): 37,920 gas
*
Expand Down
7 changes: 4 additions & 3 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
"contracts"
],
"devDependencies": {
"@noble/hashes": "1.3.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@oasisprotocol/client": "^0.1.1-alpha.2",
"@oasisprotocol/sapphire-ethers-v6": "workspace:^",
"@oasisprotocol/sapphire-hardhat": "workspace:^",
"@oasisprotocol/sapphire-paratime": "workspace:^",
"@oasisprotocol/sapphire-ethers-v6": "workspace:^",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.3",
Expand All @@ -45,6 +46,7 @@
"ethers": "6.x",
"hardhat": "^2.22.2",
"hardhat-watcher": "^2.5.0",
"micro-sr25519": "^0.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"prettier-plugin-solidity": "1.0.0-beta.24",
Expand All @@ -53,8 +55,7 @@
"solidity-coverage": "^0.8.2",
"ts-node": "^10.9.1",
"typechain": "^8.3.2",
"typescript": "^4.8.3",
"@noble/hashes": "1.3.2"
"typescript": "^4.8.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.2"
Expand Down
104 changes: 103 additions & 1 deletion contracts/test/signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ethers } from 'hardhat';

import { SigningTests__factory } from '../typechain-types/factories/contracts/tests';
import { SigningTests } from '../typechain-types/contracts/tests/SigningTests';
import * as sr25519 from 'micro-sr25519';
import { getBytes, hexlify, keccak256 } from 'ethers';

function randomBytesUnlike(len: number, orig: Buffer): Buffer {
do {
Expand Down Expand Up @@ -205,7 +207,107 @@ describe('Signing', function () {
);
});

// TODO: implement Sr25519
it('sr25519', async () => {
// Try sr25519 (alg=6)
// 32 byte context, empty message
const sha256_kp = await se.testKeygen(6, randomBytes(32));
await testSignThenVerify(
se,
6,
sha256_kp,
randomBytes(32),
EMPTY_BUFFER,
32,
0,
);

// Key derivation from polkadot test cases
// See: https://github.com/polkadot-js/wasm/blob/10010830094e7d033bd11b16c5e3bc01a7045309/packages/wasm-crypto/src/rs/sr25519.rs#L176
const secretSeed = getBytes(
'0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e',
);
const secretKey = sr25519.secretFromSeed(secretSeed);
const publicKey = sr25519.getPublicKey(secretKey);
expect(hexlify(publicKey)).eq(
'0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a',
);

// Known valid signature
const msg = new TextEncoder().encode('<Bytes>message to sign</Bytes>');
const sig = getBytes(
'0x48ce2c90e08651adfc8ecef84e916f6d1bb51ebebd16150ee12df247841a5437951ea0f9d632ca165e6ab391532e75e701be6a1caa88c8a6bcca3511f55b4183',
);
const sigSigner = getBytes(
'0xf84d048da2ddae2d9d8fd6763f469566e8817a26114f39408de15547f6d47805',
);

// Verify JS implementation matches polkadot test case signature
const isValid = sr25519.verify(msg, sig, sigSigner);
expect(isValid).eq(true);

const CONTEXT = new TextEncoder().encode('substrate');

// Verify on-chain implementation also works
const result = await se.testVerify(6, sigSigner, CONTEXT, msg, sig);
expect(result).eq(true);

// Test key generation on-chian matches JS implementation
const generatedKey = await se.testKeygen(6, secretSeed);
expect(hexlify(getBytes(generatedKey.secretKey).slice(0, 64))).eq(
hexlify(secretKey),
);
expect(generatedKey.publicKey).eq(hexlify(publicKey));

// 64 byte secret, appended with 32 byte public key
expect(getBytes(generatedKey.publicKey).length).eq(32);
expect(getBytes(generatedKey.secretKey).length).eq(96);
expect(hexlify(getBytes(generatedKey.secretKey).slice(64))).eq(
generatedKey.publicKey,
);

// JS can verify on-chain signed message
const onchainSigned = await se.testSign(
6,
generatedKey.secretKey,
CONTEXT,
msg,
);
const jsVerify = sr25519.verify(
msg,
getBytes(onchainSigned),
getBytes(generatedKey.publicKey),
);
expect(jsVerify).eq(true);
// And on-chain can verify on-chain signed message
expect(
await se.testVerify(
6,
generatedKey.publicKey,
CONTEXT,
msg,
onchainSigned,
),
).eq(true);

// JS roundtrip with on-chain generated keypair
const jsSigned = sr25519.sign(
getBytes(generatedKey.secretKey).slice(0, 64),
msg,
);
expect(sr25519.verify(msg, jsSigned, getBytes(generatedKey.publicKey))).eq(
true,
);

// on-chain verify JS signed message
const onchainVerify = await se.testVerify(
6,
generatedKey.publicKey,
CONTEXT,
msg,
jsSigned,
);
expect(onchainVerify).eq(true);
});

it('Secp256r1 (Prehashed SHA256)', async () => {
// Try Secp256r1 (alg=7)
Expand Down
Loading

0 comments on commit 61c78da

Please sign in to comment.