Skip to content

Commit ee29802

Browse files
authored
Implement bn128 precompiles (#2718)
* Implement `bn128` precompiles * Update changelog * Create `integration-tests/internal/builtin-precompiles`
1 parent af75e4d commit ee29802

File tree

9 files changed

+447
-2
lines changed

9 files changed

+447
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
[Unreleased]
88

9+
### Added
10+
- Implement `bn128` precompiles ‒ [2708](https://github.com/use-ink/ink/pull/2718)
11+
912
### Changed
10-
- Refactor contract ref generation and add automatic re-exporting - [#2710](https://github.com/use-ink/ink/pull/2710)
13+
- Refactor contract ref generation and add automatic re-exporting [#2710](https://github.com/use-ink/ink/pull/2710)
1114
- Implement and stabilize `terminate_contract`[2708](https://github.com/use-ink/ink/pull/2708)
1215

1316
## Version 6.0.0-beta
@@ -2640,4 +2643,4 @@ impl Contract {
26402643

26412644
This is useful if the `impl` block itself does not contain any ink! constructors or messages, but you
26422645
still need to access some of the "magic" provided by ink!. In the example above, you would not have
2643-
access to `emit_event` without `#[ink(impl)]`.
2646+
access to `emit_event` without `#[ink(impl)]`.

crates/engine/src/ext.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,27 @@ impl Engine {
353353
}
354354

355355
impl Engine {
356+
/// Implements the `bn128_add` G1 addition precompile.
357+
pub fn bn128_add(self, _x1: U256, _y1: U256, _x2: U256, _y2: U256) -> (U256, U256) {
358+
unimplemented!(
359+
"`bn128_add` is not implemented in the off-chain testing environment"
360+
);
361+
}
362+
363+
/// Implements the `bn128_mul` G1 scalar-mul precompile.
364+
pub fn bn128_mul(self, _x1: U256, _y1: U256, _scalar: U256) -> (U256, U256) {
365+
unimplemented!(
366+
"`bn128_mul` is not implemented in the off-chain testing environment"
367+
);
368+
}
369+
370+
/// Implements the `bn128_pairing` precompile.
371+
pub fn bn128_pairing(self, _input: &[u8]) -> bool {
372+
unimplemented!(
373+
"`bn128_pairing` is not implemented in the off-chain testing environment"
374+
);
375+
}
376+
356377
/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
357378
/// and stores the result in `output`.
358379
#[allow(clippy::arithmetic_side_effects)] // todo

crates/env/src/api.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,49 @@ where
617617
})
618618
}
619619

620+
/// Calls the `bn128_add` G1 addition precompile.
621+
///
622+
/// Inputs are affine G1 coordinates over Fq.
623+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
624+
///
625+
/// # Note
626+
///
627+
/// The precompile address is `0x06`. You can find its implementation here:
628+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
629+
pub fn bn128_add(x1: U256, y1: U256, x2: U256, y2: U256) -> (U256, U256) {
630+
<EnvInstance as OnInstance>::on_instance(|instance| {
631+
instance.bn128_add(x1, y1, x2, y2)
632+
})
633+
}
634+
635+
/// Calls the `bn128_mul` G1 scalar-mul precompile.
636+
///
637+
/// Multiplies an affine G1 point (x1, y1) by a scalar ∈ Fr.
638+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
639+
///
640+
/// # Note
641+
///
642+
/// The precompile address is `0x07`. You can find its implementation here:
643+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
644+
pub fn bn128_mul(x1: U256, y1: U256, scalar: U256) -> (U256, U256) {
645+
<EnvInstance as OnInstance>::on_instance(|instance| {
646+
instance.bn128_mul(x1, y1, scalar)
647+
})
648+
}
649+
650+
/// Calls the `bn128_pairing` precompile.
651+
///
652+
/// Input is the Solidity-ABI-encoded sequence of (G1, G2) pairs.
653+
/// Returns `true` iff the product of pairings evaluates to the identity.
654+
///
655+
/// # Note
656+
///
657+
/// The precompile address is `0x08`. You can find its implementation here:
658+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
659+
pub fn bn128_pairing(input: &[u8]) -> bool {
660+
<EnvInstance as OnInstance>::on_instance(|instance| instance.bn128_pairing(input))
661+
}
662+
620663
/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
621664
/// and stores the result in `output`.
622665
///

crates/env/src/backend.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,39 @@ pub trait EnvBackend {
173173
H: CryptoHash,
174174
T: scale::Encode;
175175

176+
/// Calls the `bn128_add` G1 addition precompile.
177+
///
178+
/// Inputs are affine G1 coordinates over Fq.
179+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
180+
///
181+
/// # Note
182+
///
183+
/// The precompile address is `0x06`. You can find its implementation here:
184+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
185+
fn bn128_add(&mut self, x1: U256, y1: U256, x2: U256, y2: U256) -> (U256, U256);
186+
187+
/// Calls the `bn128_mul` G1 scalar-mul precompile.
188+
///
189+
/// Multiplies an affine G1 point (x1, y1) by a scalar ∈ Fr.
190+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
191+
///
192+
/// # Note
193+
///
194+
/// The precompile address is `0x07`. You can find its implementation here:
195+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
196+
fn bn128_mul(&mut self, x1: U256, y1: U256, scalar: U256) -> (U256, U256);
197+
198+
/// Calls the `bn128_pairing` precompile.
199+
///
200+
/// Input is the Solidity-ABI-encoded sequence of (G1, G2) pairs.
201+
/// Returns `true` iff the product of pairings evaluates to the identity.
202+
///
203+
/// # Note
204+
///
205+
/// The precompile address is `0x08`. You can find its implementation here:
206+
/// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/precompiles/builtin/bn128.rs>
207+
fn bn128_pairing(&mut self, input: &[u8]) -> bool;
208+
176209
/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
177210
/// and stores the result in `output`.
178211
fn ecdsa_recover(

crates/env/src/engine/off_chain/impls.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,24 @@ impl EnvBackend for EnvInstance {
456456
<H as CryptoHash>::hash(enc_input, output)
457457
}
458458

459+
fn bn128_add(&mut self, _x1: U256, _y1: U256, _x2: U256, _y2: U256) -> (U256, U256) {
460+
unimplemented!(
461+
"`bn128_add` is not implemented in the off-chain testing environment"
462+
);
463+
}
464+
465+
fn bn128_mul(&mut self, _x1: U256, _y1: U256, _scalar: U256) -> (U256, U256) {
466+
unimplemented!(
467+
"`bn128_mul` is not implemented in the off-chain testing environment"
468+
);
469+
}
470+
471+
fn bn128_pairing(&mut self, _input: &[u8]) -> bool {
472+
unimplemented!(
473+
"`bn128_pairing` is not implemented in the off-chain testing environment"
474+
);
475+
}
476+
459477
#[allow(clippy::arithmetic_side_effects)] // todo
460478
fn ecdsa_recover(
461479
&mut self,

crates/env/src/engine/on_chain/pallet_revive.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,80 @@ impl EnvBackend for EnvInstance {
868868
<H as CryptoHash>::hash(enc_input, output)
869869
}
870870

871+
fn bn128_add(&mut self, x1: U256, y1: U256, x2: U256, y2: U256) -> (U256, U256) {
872+
const BN128_ADD: [u8; 20] =
873+
hex_literal::hex!("0000000000000000000000000000000000000006");
874+
875+
let mut input = [0u8; 128];
876+
input[..32].copy_from_slice(&x1.to_big_endian());
877+
input[32..64].copy_from_slice(&y1.to_big_endian());
878+
input[64..96].copy_from_slice(&x2.to_big_endian());
879+
input[96..128].copy_from_slice(&y2.to_big_endian());
880+
881+
let mut output = [0u8; 64];
882+
let _ = ext::call(
883+
CallFlags::empty(),
884+
&BN128_ADD,
885+
u64::MAX, // `ref_time` to devote for execution. `u64::MAX` = all
886+
u64::MAX, // `proof_size` to devote for execution. `u64::MAX` = all
887+
&[u8::MAX; 32], // No deposit limit.
888+
&U256::zero().to_little_endian(), // Value transferred to the contract.
889+
&input[..],
890+
Some(&mut &mut output[..]),
891+
);
892+
let x3 = U256::from_big_endian(&output[..32]);
893+
let y3 = U256::from_big_endian(&output[32..64]);
894+
(x3, y3)
895+
}
896+
897+
fn bn128_mul(&mut self, x1: U256, y1: U256, scalar: U256) -> (U256, U256) {
898+
const BN128_ADD: [u8; 20] =
899+
hex_literal::hex!("0000000000000000000000000000000000000007");
900+
901+
let mut input = [0u8; 128];
902+
input[..32].copy_from_slice(&x1.to_big_endian());
903+
input[32..64].copy_from_slice(&y1.to_big_endian());
904+
input[64..96].copy_from_slice(&scalar.to_big_endian());
905+
906+
let mut output = [0u8; 64];
907+
let _ = ext::call(
908+
CallFlags::empty(),
909+
&BN128_ADD,
910+
u64::MAX, // `ref_time` to devote for execution. `u64::MAX` = all
911+
u64::MAX, // `proof_size` to devote for execution. `u64::MAX` = all
912+
&[u8::MAX; 32], // No deposit limit.
913+
&U256::zero().to_little_endian(), // Value transferred to the contract.
914+
&input[..],
915+
Some(&mut &mut output[..]),
916+
);
917+
let x2 = U256::from_big_endian(&output[..32]);
918+
let y2 = U256::from_big_endian(&output[32..64]);
919+
(x2, y2)
920+
}
921+
922+
fn bn128_pairing(&mut self, input: &[u8]) -> bool {
923+
const BN128_ADD: [u8; 20] =
924+
hex_literal::hex!("0000000000000000000000000000000000000008");
925+
926+
let mut output = [0u8; 32];
927+
let _ = ext::call(
928+
CallFlags::empty(),
929+
&BN128_ADD,
930+
u64::MAX, // `ref_time` to devote for execution. `u64::MAX` = all
931+
u64::MAX, // `proof_size` to devote for execution. `u64::MAX` = all
932+
&[u8::MAX; 32], // No deposit limit.
933+
&U256::zero().to_little_endian(), // Value transferred to the contract.
934+
&input[..],
935+
Some(&mut &mut output[..]),
936+
);
937+
if output[31] == 1 {
938+
debug_assert_eq!(&output[..31], [0u8; 31]);
939+
return true;
940+
}
941+
debug_assert_eq!(&output[..32], [0u8; 32]);
942+
false
943+
}
944+
871945
fn ecdsa_recover(
872946
&mut self,
873947
signature: &[u8; 65],

crates/ink/src/env_access.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,42 @@ where
998998
output
999999
}
10001000

1001+
/// Calls the `bn128_add` G1 addition precompile.
1002+
///
1003+
/// Inputs are affine G1 coordinates over Fq.
1004+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
1005+
///
1006+
/// # Note
1007+
///
1008+
/// For more details visit: [`ink_env::bn128_add`]
1009+
pub fn bn128_add(self, x1: U256, y1: U256, x2: U256, y2: U256) -> (U256, U256) {
1010+
ink_env::bn128_add(x1, y1, x2, y2)
1011+
}
1012+
1013+
/// Calls the `bn128_mul` G1 scalar-mul precompile.
1014+
///
1015+
/// Multiplies an affine G1 point (x1, y1) by a scalar ∈ Fr.
1016+
/// Returns the resulting affine point or (0, 0) if the result is ∞ / invalid.
1017+
///
1018+
/// # Note
1019+
///
1020+
/// For more details visit: [`ink_env::bn128_mul`]
1021+
pub fn bn128_mul(self, x1: U256, y1: U256, scalar: U256) -> (U256, U256) {
1022+
ink_env::bn128_mul(x1, y1, scalar)
1023+
}
1024+
1025+
/// Calls the `bn128_pairing` precompile.
1026+
///
1027+
/// Input is the Solidity-ABI-encoded sequence of (G1, G2) pairs.
1028+
/// Returns `true` iff the product of pairings evaluates to the identity.
1029+
///
1030+
/// # Note
1031+
///
1032+
/// For more details visit: [`ink_env::bn128_pairing`]
1033+
pub fn bn128_pairing(self, input: &[u8]) -> bool {
1034+
ink_env::bn128_pairing(input)
1035+
}
1036+
10011037
/// Recovers the compressed ECDSA public key for given `signature` and `message_hash`,
10021038
/// and stores the result in `output`.
10031039
///
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "builtin_precompiles"
3+
description = "E2E tests for various builtin precompiles"
4+
version = "6.0.0-beta"
5+
authors = ["Use Ink <[email protected]>"]
6+
edition = "2021"
7+
publish = false
8+
9+
[dependencies]
10+
ink = { path = "../../../crates/ink", default-features = false }
11+
12+
[dev-dependencies]
13+
ink_e2e = { path = "../../../crates/e2e" }
14+
hex-literal = "1"
15+
impl-serde = { version = "0.5.0", default-features = false }
16+
17+
[lib]
18+
path = "lib.rs"
19+
20+
[features]
21+
default = ["std"]
22+
std = [
23+
"ink/std",
24+
]
25+
ink-as-dependency = []
26+
e2e-tests = []
27+
28+
[package.metadata.ink-lang]
29+
abi = "ink"

0 commit comments

Comments
 (0)