diff --git a/Cargo.lock b/Cargo.lock index ff01eac7..7aab7ba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1726,6 +1726,15 @@ dependencies = [ "version_check 0.9.2", ] +[[package]] +name = "incrementalmerkletree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d75fb342984cc8cea665a9ef5607b4956569839ca098172082fa1b19bdaf02" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.0" @@ -4506,6 +4515,7 @@ dependencies = [ "group", "halo2", "hex", + "incrementalmerkletree", "itertools 0.10.1", "jubjub", "lazy_static", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 3f6bffc0..bc6c1f02 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -46,6 +46,7 @@ zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "0 bigint = "4" uint = "0.9.1" bls12_381 = "0.5.0" +incrementalmerkletree = "0.1.0" proptest = { version = "0.10", optional = true } proptest-derive = { version = "0.3.0", optional = true } diff --git a/zebra-chain/src/orchard/tests.rs b/zebra-chain/src/orchard/tests.rs index 829ffe5d..2b8339b2 100644 --- a/zebra-chain/src/orchard/tests.rs +++ b/zebra-chain/src/orchard/tests.rs @@ -1,3 +1,4 @@ mod preallocate; mod prop; -pub mod test_vectors; +mod test_vectors; +mod tree; diff --git a/zebra-chain/src/orchard/tests/test_vectors.rs b/zebra-chain/src/orchard/tests/test_vectors.rs index 1de76252..26c3397b 100644 --- a/zebra-chain/src/orchard/tests/test_vectors.rs +++ b/zebra-chain/src/orchard/tests/test_vectors.rs @@ -167,3 +167,355 @@ pub const EMPTY_ROOTS: [[u8; 32]; 33] = [ 0xd8, 0x2f, ], ]; + +// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_merkle_tree.py +pub const COMMITMENTS: [[[u8; 32]; 5]; 10] = [ + [ + [ + 0x68, 0x13, 0x5c, 0xf4, 0x99, 0x33, 0x22, 0x90, 0x99, 0xa4, 0x4e, 0xc9, 0x9a, 0x75, + 0xe1, 0xe1, 0xcb, 0x46, 0x40, 0xf9, 0xb5, 0xbd, 0xec, 0x6b, 0x32, 0x23, 0x85, 0x6f, + 0xea, 0x16, 0x39, 0x0a, + ], + [ + 0x78, 0x31, 0x50, 0x08, 0xfb, 0x29, 0x98, 0xb4, 0x30, 0xa5, 0x73, 0x1d, 0x67, 0x26, + 0x20, 0x7d, 0xc0, 0xf0, 0xec, 0x81, 0xea, 0x64, 0xaf, 0x5c, 0xf6, 0x12, 0x95, 0x69, + 0x01, 0xe7, 0x2f, 0x0e, + ], + [ + 0xee, 0x94, 0x88, 0x05, 0x3a, 0x30, 0xc5, 0x96, 0xb4, 0x30, 0x14, 0x10, 0x5d, 0x34, + 0x77, 0xe6, 0xf5, 0x78, 0xc8, 0x92, 0x40, 0xd1, 0xd1, 0xee, 0x17, 0x43, 0xb7, 0x7b, + 0xb6, 0xad, 0xc4, 0x0a, + ], + [ + 0x9d, 0xdc, 0xe7, 0xf0, 0x65, 0x01, 0xf3, 0x63, 0x76, 0x8c, 0x5b, 0xca, 0x3f, 0x26, + 0x46, 0x60, 0x83, 0x4d, 0x4d, 0xf4, 0x46, 0xd1, 0x3e, 0xfc, 0xd7, 0xc6, 0xf1, 0x7b, + 0x16, 0x7a, 0xac, 0x1a, + ], + [ + 0xbd, 0x86, 0x16, 0x81, 0x1c, 0x6f, 0x5f, 0x76, 0x9e, 0xa4, 0x53, 0x9b, 0xba, 0xff, + 0x0f, 0x19, 0x8a, 0x6c, 0xdf, 0x3b, 0x28, 0x0d, 0xd4, 0x99, 0x26, 0x16, 0x3b, 0xd5, + 0x3f, 0x53, 0xa1, 0x21, + ], + ], + [ + [ + 0x31, 0x11, 0xbe, 0x48, 0x8a, 0xfc, 0x50, 0x4c, 0x11, 0xaf, 0x2d, 0xfb, 0x35, 0xda, + 0x93, 0x2f, 0x65, 0x72, 0xee, 0xcd, 0x19, 0xa6, 0xc8, 0x54, 0x09, 0x5b, 0x01, 0x68, + 0x30, 0x88, 0xe8, 0x25, + ], + [ + 0x6f, 0x01, 0x84, 0x37, 0xde, 0x72, 0x2d, 0xa9, 0xa9, 0x88, 0x1c, 0x6d, 0x17, 0x6f, + 0xf4, 0x19, 0x60, 0x84, 0x4e, 0x6e, 0x0a, 0x3a, 0xd6, 0xcf, 0x8e, 0xdb, 0x6a, 0xf4, + 0xf1, 0xc2, 0x8a, 0x34, + ], + [ + 0x44, 0xc3, 0xb9, 0x59, 0xf9, 0xe5, 0x08, 0x0d, 0xfd, 0x55, 0x0a, 0x84, 0x82, 0x58, + 0x34, 0xfb, 0x39, 0x2b, 0x6e, 0xe6, 0x61, 0xf8, 0x9b, 0x2b, 0xfb, 0xa5, 0xfb, 0x45, + 0x44, 0x4e, 0x67, 0x3a, + ], + [ + 0x5e, 0xc3, 0x6d, 0xce, 0xb0, 0xa0, 0xb1, 0xf8, 0x1e, 0x3f, 0x16, 0x72, 0x49, 0x91, + 0x78, 0x1f, 0xae, 0xfa, 0x68, 0x38, 0x24, 0xe7, 0x4e, 0x81, 0x20, 0xf7, 0x46, 0x53, + 0x08, 0xe9, 0xd9, 0x21, + ], + [ + 0x9c, 0x35, 0xf2, 0x03, 0x20, 0x36, 0x09, 0x92, 0xf6, 0xa8, 0x8a, 0xa5, 0xc8, 0x70, + 0xcd, 0xb5, 0x9c, 0x45, 0x97, 0xfe, 0x08, 0xfb, 0xf8, 0xe1, 0xf3, 0xb2, 0x14, 0x73, + 0x3d, 0x89, 0x23, 0x2a, + ], + ], + [ + [ + 0xca, 0x38, 0x0a, 0x17, 0xb6, 0xa0, 0xf2, 0x4e, 0xf0, 0x6d, 0x13, 0x9b, 0xaa, 0x1b, + 0x70, 0xe6, 0x78, 0xa7, 0x3d, 0x0e, 0x65, 0x78, 0x58, 0x04, 0xd8, 0x8c, 0x96, 0x6a, + 0xcf, 0x10, 0x37, 0x38, + ], + [ + 0xfe, 0xca, 0x8d, 0xf1, 0xc0, 0x4c, 0xda, 0x0c, 0xfc, 0xdc, 0x23, 0x4c, 0x17, 0x14, + 0x71, 0xdf, 0x7a, 0xad, 0x90, 0xac, 0x9e, 0x28, 0x64, 0xb6, 0xe1, 0xbe, 0x0d, 0xfc, + 0x6a, 0x40, 0x41, 0x23, + ], + [ + 0x83, 0x7d, 0x8d, 0xc9, 0xa8, 0x99, 0x1c, 0x17, 0x8d, 0x65, 0x02, 0x85, 0x0c, 0x1f, + 0x91, 0xb1, 0xd4, 0x1a, 0x97, 0x3e, 0xf5, 0xa9, 0xec, 0x4b, 0x10, 0xbf, 0x46, 0x24, + 0x67, 0xa7, 0xd4, 0x21, + ], + [ + 0xb6, 0x7b, 0xf5, 0x11, 0x92, 0xe3, 0x0e, 0x8b, 0x39, 0x9c, 0xed, 0xef, 0xd8, 0xb6, + 0x4f, 0x23, 0x86, 0xad, 0x1c, 0xac, 0x70, 0xbd, 0xcc, 0xd7, 0xca, 0xc1, 0x95, 0x1a, + 0x3e, 0x86, 0xb5, 0x37, + ], + [ + 0xce, 0x62, 0x6d, 0x31, 0x28, 0xca, 0x6c, 0x8f, 0xd2, 0x72, 0x21, 0x59, 0x12, 0xde, + 0x64, 0xfb, 0x74, 0xc4, 0x5d, 0x56, 0xf4, 0x37, 0x4c, 0xe5, 0xbf, 0x05, 0x37, 0xee, + 0x2e, 0x38, 0x20, 0x07, + ], + ], + [ + [ + 0xfd, 0x22, 0x61, 0xb6, 0x2d, 0x4a, 0x70, 0x96, 0x40, 0x05, 0x44, 0x72, 0x03, 0x34, + 0x90, 0xb8, 0x3d, 0x18, 0x42, 0x02, 0xe0, 0x26, 0x72, 0xc2, 0xff, 0x12, 0x76, 0x6c, + 0x4e, 0x7d, 0x4f, 0x0b, + ], + [ + 0xef, 0xc2, 0xc8, 0xe1, 0x29, 0xb5, 0x1d, 0xc6, 0x2b, 0xcb, 0xdc, 0x3f, 0x3e, 0x1a, + 0x58, 0x2c, 0xd2, 0x83, 0xc7, 0x98, 0x07, 0x57, 0x53, 0xcd, 0x83, 0x12, 0x17, 0x33, + 0x81, 0xfa, 0x1f, 0x34, + ], + [ + 0x22, 0x3b, 0xc8, 0xe6, 0x9d, 0x13, 0x2b, 0xdd, 0x85, 0x07, 0x0c, 0xaf, 0x9e, 0x12, + 0xa1, 0xbb, 0xe2, 0xe1, 0x4a, 0x63, 0x0b, 0xec, 0x08, 0x44, 0x18, 0x1c, 0x01, 0x4e, + 0x57, 0x77, 0xd1, 0x15, + ], + [ + 0xc2, 0x0a, 0x21, 0xa5, 0xe2, 0x0a, 0x76, 0xd6, 0xc5, 0x05, 0xe9, 0x67, 0xcb, 0x9d, + 0x50, 0xd7, 0x53, 0xe3, 0xd1, 0x35, 0x3c, 0xce, 0x32, 0x6e, 0xef, 0x55, 0x5f, 0xa3, + 0x31, 0xb6, 0x9c, 0x21, + ], + [ + 0xd0, 0xc7, 0x3f, 0x0f, 0x98, 0x76, 0x3b, 0xed, 0x8b, 0x14, 0x80, 0x0f, 0x37, 0x88, + 0xc9, 0x80, 0x83, 0xc8, 0x0e, 0x1c, 0x88, 0x90, 0x46, 0x64, 0x4c, 0x95, 0x6f, 0x01, + 0xe2, 0x4b, 0x1d, 0x38, + ], + ], + [ + [ + 0xcf, 0x79, 0xd1, 0xb1, 0x1e, 0x3b, 0xa2, 0x8f, 0xc2, 0x14, 0x8f, 0x21, 0xa6, 0x6f, + 0xe1, 0x89, 0xe0, 0xff, 0xb6, 0x78, 0x60, 0xd4, 0x91, 0x26, 0x5c, 0x18, 0x5a, 0x35, + 0xb3, 0x9b, 0xb5, 0x2b, + ], + [ + 0x69, 0x00, 0xdb, 0x33, 0x01, 0xa4, 0xfd, 0x80, 0x50, 0x2f, 0xa7, 0xf0, 0xcc, 0x83, + 0x42, 0x6e, 0x7a, 0xf2, 0x23, 0x1f, 0xcf, 0x61, 0x2c, 0x0d, 0x25, 0xb6, 0x84, 0x88, + 0x81, 0x3f, 0xcb, 0x11, + ], + [ + 0xfb, 0x46, 0x7e, 0xfc, 0xa6, 0xe1, 0xd5, 0x48, 0xc9, 0x30, 0x48, 0x75, 0xfc, 0x33, + 0xd3, 0xe5, 0x64, 0xf2, 0x9e, 0xe0, 0x99, 0x7e, 0xbd, 0x18, 0x7d, 0x98, 0x59, 0x10, + 0x03, 0x4d, 0xbc, 0x04, + ], + [ + 0x39, 0x0e, 0xaf, 0x40, 0x8e, 0x20, 0x9e, 0xf0, 0x69, 0xe1, 0xb3, 0xe6, 0xbc, 0x63, + 0xba, 0xce, 0x32, 0xd2, 0xf0, 0x68, 0x39, 0xd1, 0xfe, 0xd8, 0x18, 0x17, 0xbf, 0xcc, + 0x1f, 0x6f, 0xa6, 0x14, + ], + [ + 0xee, 0x60, 0xdf, 0x35, 0x19, 0xe0, 0xd9, 0xb0, 0x77, 0xf3, 0xcc, 0x29, 0x7c, 0x62, + 0x0e, 0xdd, 0xd5, 0x2e, 0x1e, 0x22, 0xb3, 0x7c, 0xe5, 0x5e, 0xc9, 0xf8, 0xe3, 0xc3, + 0x37, 0x0f, 0xb3, 0x11, + ], + ], + [ + [ + 0x19, 0xa9, 0xe5, 0x13, 0x82, 0x6e, 0x11, 0x06, 0xa3, 0xa9, 0x5f, 0x5e, 0x7a, 0x71, + 0xc0, 0xd1, 0xdf, 0xc4, 0xc2, 0x21, 0x25, 0xe6, 0xd6, 0x45, 0xc2, 0x79, 0x78, 0x84, + 0x6b, 0xc4, 0x4b, 0x37, + ], + [ + 0xcf, 0xf9, 0x98, 0x0d, 0xea, 0xae, 0xb9, 0x9a, 0xab, 0x22, 0xa8, 0x66, 0x92, 0x9d, + 0xc2, 0x4d, 0x1d, 0x80, 0x56, 0x5e, 0x7a, 0x2c, 0xdf, 0x74, 0x5d, 0x0a, 0xb9, 0x67, + 0x6e, 0xdf, 0x2c, 0x0d, + ], + [ + 0x98, 0x35, 0xdf, 0xe3, 0x97, 0x53, 0xce, 0xbb, 0xdf, 0x66, 0xc6, 0x0b, 0x44, 0xed, + 0x14, 0x4a, 0xe3, 0xbb, 0x06, 0x90, 0x57, 0xe4, 0x43, 0x1d, 0x84, 0x29, 0xf3, 0x61, + 0xc6, 0x9d, 0x21, 0x0f, + ], + [ + 0xc5, 0xad, 0x66, 0x19, 0xe9, 0x6c, 0x95, 0xfd, 0xe4, 0x69, 0xf4, 0xf7, 0x20, 0x2e, + 0xb0, 0x31, 0xac, 0x95, 0xd8, 0xd2, 0x3f, 0x31, 0x03, 0x16, 0x6a, 0x44, 0x4c, 0xf1, + 0x2c, 0x4b, 0x81, 0x3f, + ], + [ + 0x8a, 0x8c, 0x06, 0x1c, 0x64, 0xea, 0x83, 0x37, 0xf8, 0x11, 0x11, 0xdd, 0x38, 0x6b, + 0xef, 0x22, 0xfe, 0x0f, 0x52, 0xe2, 0xc6, 0xa7, 0x8f, 0xbc, 0xc0, 0xd9, 0x0d, 0xcf, + 0x28, 0xb2, 0xdf, 0x1b, + ], + ], + [ + [ + 0x7a, 0x3c, 0x6d, 0xc5, 0x93, 0xb9, 0x65, 0xa4, 0xa3, 0x11, 0x8a, 0x0e, 0x9e, 0x80, + 0xd0, 0x78, 0x2c, 0xb4, 0x13, 0x99, 0x79, 0x0b, 0xa7, 0xf6, 0x49, 0x77, 0x1c, 0x74, + 0x43, 0xa3, 0x6d, 0x01, + ], + [ + 0x0b, 0xbb, 0x5a, 0xfb, 0xb7, 0xc5, 0x00, 0x5e, 0x14, 0x9f, 0x8b, 0x5b, 0xf5, 0xfe, + 0x35, 0x40, 0xb3, 0x7d, 0x31, 0xe3, 0xc2, 0xb2, 0x14, 0x33, 0x73, 0x59, 0xda, 0xe3, + 0x31, 0x30, 0x23, 0x11, + ], + [ + 0x93, 0x35, 0x52, 0xa5, 0xc9, 0xd7, 0x3e, 0x50, 0xf6, 0xdb, 0xcf, 0xa4, 0x1e, 0xa3, + 0xd0, 0xa5, 0x29, 0x46, 0xb1, 0x87, 0x8e, 0xda, 0x5c, 0x96, 0xd7, 0xd8, 0xb0, 0xe4, + 0x71, 0x68, 0x0b, 0x27, + ], + [ + 0x62, 0x8b, 0xa8, 0x4e, 0x08, 0x63, 0xb6, 0x27, 0x72, 0x7d, 0xf9, 0xa1, 0x98, 0xb0, + 0x9d, 0xd0, 0xe3, 0x1a, 0xff, 0x7c, 0xba, 0xec, 0x87, 0xcd, 0x93, 0x3d, 0x0f, 0x76, + 0x58, 0xea, 0xcb, 0x3a, + ], + [ + 0x90, 0x5f, 0x7a, 0x5e, 0x88, 0xa0, 0x16, 0xa1, 0x66, 0x8d, 0xdd, 0x41, 0xea, 0xcb, + 0x91, 0x28, 0x78, 0xc7, 0xcb, 0xeb, 0x42, 0xa9, 0x31, 0x90, 0x33, 0x98, 0xe0, 0x5f, + 0xdc, 0xe6, 0x8b, 0x29, + ], + ], + [ + [ + 0x94, 0x92, 0xc9, 0x01, 0x95, 0x73, 0x31, 0xd5, 0xe7, 0x94, 0xe5, 0x9a, 0xd4, 0x73, + 0x67, 0x51, 0x68, 0x0f, 0x2f, 0xde, 0xca, 0x12, 0x3f, 0xf3, 0xcb, 0xe0, 0xc6, 0x3a, + 0x2d, 0xc8, 0x61, 0x36, + ], + [ + 0x5c, 0xd8, 0x54, 0x1a, 0x4c, 0x92, 0x0e, 0x4d, 0x7e, 0xac, 0x65, 0x71, 0xa6, 0xe9, + 0x3b, 0x40, 0xcb, 0xda, 0x99, 0xf3, 0x48, 0xf1, 0x70, 0x40, 0x0f, 0x91, 0x81, 0xb8, + 0x1e, 0xb2, 0xcf, 0x17, + ], + [ + 0x65, 0x90, 0x97, 0x4e, 0xcb, 0x6e, 0x62, 0xb4, 0x8d, 0x21, 0x9f, 0xcf, 0xf7, 0x5d, + 0x6d, 0xf8, 0x55, 0x8e, 0xb6, 0xb5, 0xd4, 0x3e, 0x17, 0x3c, 0x88, 0x45, 0xb0, 0x59, + 0x31, 0x5c, 0x56, 0x10, + ], + [ + 0x0c, 0xe2, 0xec, 0x64, 0xa3, 0xd3, 0x5a, 0xca, 0x95, 0x4f, 0x9f, 0x77, 0xfc, 0xb6, + 0x96, 0xb2, 0x13, 0xb1, 0x09, 0xc9, 0xef, 0x55, 0x14, 0xf2, 0x84, 0x1a, 0xd0, 0xd9, + 0x6f, 0x49, 0x17, 0x38, + ], + [ + 0x2c, 0x02, 0x15, 0x93, 0x8c, 0x51, 0x65, 0xff, 0xd4, 0x67, 0x0d, 0xd5, 0xec, 0xff, + 0xae, 0xb2, 0x4c, 0x4c, 0x35, 0xde, 0x59, 0x4e, 0xad, 0xc8, 0xf4, 0xd4, 0xd6, 0x0e, + 0xac, 0xb9, 0xc0, 0x31, + ], + ], + [ + [ + 0xe6, 0x58, 0x63, 0x2a, 0x79, 0x0b, 0x84, 0xc2, 0x78, 0x7f, 0x87, 0x33, 0x28, 0x3f, + 0xe6, 0xd2, 0x32, 0x60, 0x0d, 0x73, 0xba, 0x89, 0x79, 0x5b, 0xad, 0x40, 0x0c, 0x4b, + 0x9b, 0x77, 0xa0, 0x29, + ], + [ + 0x4e, 0x15, 0x6a, 0x3b, 0x37, 0x24, 0x5d, 0xe5, 0xd9, 0x77, 0x64, 0xe1, 0x7d, 0x8c, + 0x46, 0x46, 0x70, 0x38, 0xd8, 0xc7, 0xae, 0x5c, 0xee, 0x82, 0x25, 0xa1, 0xe2, 0xa2, + 0xad, 0x7f, 0x4a, 0x25, + ], + [ + 0xdd, 0x8e, 0x1a, 0xf4, 0x86, 0xfd, 0x57, 0x39, 0x3c, 0x41, 0xbd, 0x93, 0x83, 0xc8, + 0x76, 0x3d, 0x17, 0xfe, 0x14, 0xeb, 0xed, 0x7b, 0x0f, 0x2b, 0x08, 0x42, 0x96, 0x30, + 0x1f, 0x72, 0xbd, 0x07, + ], + [ + 0x92, 0x78, 0xa1, 0xa9, 0x69, 0x31, 0x11, 0xfa, 0x4c, 0x47, 0x00, 0x70, 0xd6, 0x2c, + 0xef, 0x52, 0x67, 0x61, 0x1f, 0xec, 0x8f, 0xeb, 0xad, 0x0e, 0x3c, 0x97, 0x73, 0xb7, + 0xef, 0x8f, 0x02, 0x1d, + ], + [ + 0x44, 0x37, 0x42, 0x53, 0xe5, 0x53, 0x0f, 0x77, 0x64, 0xfd, 0x67, 0x04, 0xe9, 0xb7, + 0xb5, 0x06, 0x4a, 0x3d, 0x5b, 0xcb, 0x52, 0x5a, 0xff, 0x1d, 0xab, 0xde, 0x9f, 0x09, + 0x1c, 0xa1, 0xb6, 0x13, + ], + ], + [ + [ + 0x80, 0x23, 0x61, 0x98, 0x92, 0x47, 0xb1, 0xb6, 0x2e, 0xd1, 0xce, 0xa5, 0x5a, 0xab, + 0x89, 0xec, 0x29, 0x87, 0xf3, 0x05, 0x2f, 0x1d, 0x1a, 0x29, 0x84, 0x60, 0x9d, 0x1f, + 0x1e, 0x80, 0xd6, 0x00, + ], + [ + 0xfa, 0xb8, 0xed, 0x98, 0x20, 0xb7, 0x9a, 0x0d, 0xe9, 0x96, 0xcd, 0x0e, 0x8f, 0x52, + 0xb2, 0x4b, 0x02, 0xec, 0x89, 0x5f, 0x3f, 0xce, 0x56, 0xa4, 0x67, 0x79, 0xb5, 0x03, + 0x09, 0x83, 0x5a, 0x23, + ], + [ + 0xd9, 0xfe, 0x9f, 0x36, 0x08, 0xa6, 0xc6, 0xff, 0x09, 0x93, 0x58, 0xa6, 0x94, 0xf6, + 0xae, 0xce, 0x4d, 0xa0, 0xc5, 0x35, 0x62, 0x8a, 0x88, 0x82, 0x43, 0x52, 0x9c, 0xa9, + 0xb1, 0xf7, 0xa8, 0x10, + ], + [ + 0x1f, 0x01, 0xf1, 0xf8, 0xf4, 0xb5, 0x64, 0xd8, 0xb0, 0xff, 0xd3, 0x2a, 0xff, 0x11, + 0x24, 0x3d, 0xee, 0x25, 0x82, 0xb4, 0xa3, 0xef, 0xbb, 0xce, 0xc9, 0x20, 0x02, 0x6c, + 0x5e, 0x1f, 0x44, 0x1e, + ], + [ + 0xa0, 0x65, 0x87, 0xcc, 0xc0, 0x3f, 0x14, 0x92, 0x94, 0x65, 0xcd, 0x3d, 0x93, 0x37, + 0x90, 0xda, 0xb1, 0x23, 0xae, 0x4e, 0x57, 0x55, 0x8a, 0x7a, 0xf4, 0xfc, 0x06, 0x1e, + 0x6b, 0x46, 0x7f, 0x1c, + ], + ], +]; + +pub struct TestVector { + pub anchor: [u8; 32], +} + +// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_merkle_tree.py +pub const ROOTS: [TestVector; 10] = [ + TestVector { + anchor: [ + 0xc8, 0x75, 0xbe, 0x2d, 0x60, 0x87, 0x3f, 0x8b, 0xcd, 0xeb, 0x91, 0x28, 0x2e, 0x64, + 0x2e, 0x0c, 0xc6, 0x5f, 0xf7, 0xd0, 0x64, 0x2d, 0x13, 0x7b, 0x28, 0xcf, 0x28, 0xcc, + 0x9c, 0x52, 0x7f, 0x0e, + ], + }, + TestVector { + anchor: [ + 0xf2, 0x49, 0x6f, 0x72, 0xdd, 0x17, 0xc3, 0x1b, 0xd9, 0x42, 0x83, 0x32, 0xf4, 0xc0, + 0x4f, 0x01, 0xd5, 0xd4, 0x7f, 0xc4, 0xf5, 0x8a, 0x8b, 0xa3, 0x65, 0xe8, 0x7b, 0xad, + 0x50, 0x1a, 0x46, 0x10, + ], + }, + TestVector { + anchor: [ + 0xfe, 0x29, 0x24, 0xb2, 0x64, 0x60, 0xa6, 0xd2, 0x53, 0xea, 0xba, 0xcb, 0x8b, 0xaf, + 0x24, 0x1e, 0x2f, 0x7b, 0xaf, 0x59, 0x46, 0x16, 0xd0, 0x8b, 0xb0, 0x98, 0x34, 0xd5, + 0xf1, 0xae, 0x04, 0x18, + ], + }, + TestVector { + anchor: [ + 0x3c, 0x18, 0xb6, 0x31, 0xde, 0xa5, 0x35, 0x62, 0xf1, 0xfd, 0x7a, 0xfa, 0xeb, 0x88, + 0x2d, 0x4f, 0x1f, 0xce, 0xfe, 0x2f, 0x17, 0xa0, 0xf7, 0x0e, 0xb8, 0xcc, 0xf9, 0xb7, + 0xb8, 0xfc, 0x43, 0x14, + ], + }, + TestVector { + anchor: [ + 0x44, 0x33, 0xe8, 0xfe, 0xfb, 0xc7, 0x68, 0xcf, 0x4f, 0xae, 0x51, 0x2f, 0x63, 0x97, + 0x82, 0xee, 0x6b, 0xbd, 0x83, 0x21, 0xfc, 0xd8, 0x9c, 0x5b, 0xde, 0xfb, 0x6d, 0x8f, + 0xce, 0x8d, 0xd9, 0x1b, + ], + }, + TestVector { + anchor: [ + 0xc2, 0x17, 0x65, 0x74, 0xdc, 0x6c, 0x15, 0x84, 0xa8, 0x36, 0xee, 0x58, 0x94, 0x54, + 0x26, 0xe3, 0xbd, 0x76, 0x7f, 0x7f, 0xa4, 0x90, 0xed, 0x68, 0xee, 0xdc, 0x88, 0xc2, + 0xaa, 0xdb, 0xbb, 0x21, + ], + }, + TestVector { + anchor: [ + 0x71, 0x83, 0x98, 0x6f, 0x46, 0xfc, 0x71, 0x28, 0x5e, 0x3b, 0xcf, 0x81, 0x47, 0x3d, + 0x3d, 0x2d, 0x78, 0xf3, 0x1f, 0x19, 0xa5, 0xea, 0xa3, 0xb6, 0x2f, 0xb9, 0x06, 0x20, + 0x4f, 0x2e, 0x2a, 0x31, + ], + }, + TestVector { + anchor: [ + 0xa3, 0x6e, 0xe6, 0xa9, 0x71, 0x99, 0xaa, 0x81, 0x4a, 0xb9, 0xba, 0x8c, 0xf7, 0x2e, + 0xc9, 0x88, 0x44, 0x2a, 0xc6, 0x67, 0x31, 0xcc, 0x1d, 0xb7, 0x0d, 0xeb, 0x55, 0x19, + 0x9d, 0x75, 0x4f, 0x13, + ], + }, + TestVector { + anchor: [ + 0x6e, 0xff, 0x53, 0xfe, 0x45, 0xb4, 0x0c, 0x37, 0xfe, 0x29, 0x7b, 0xf8, 0xbe, 0x34, + 0x5f, 0x33, 0xcd, 0x6c, 0xa5, 0x81, 0x07, 0x5a, 0xc8, 0xab, 0xc7, 0xfb, 0x11, 0xa8, + 0xb1, 0x68, 0x71, 0x06, + ], + }, + TestVector { + anchor: [ + 0x15, 0x61, 0x18, 0xf7, 0xa5, 0xd8, 0xbb, 0x90, 0xb5, 0xf2, 0xd3, 0x3a, 0x71, 0xb5, + 0x15, 0xb7, 0xf4, 0xbe, 0x7a, 0x98, 0xb0, 0xff, 0x4e, 0xc4, 0x3a, 0x16, 0x70, 0x03, + 0xb0, 0x72, 0xe4, 0x34, + ], + }, +]; diff --git a/zebra-chain/src/orchard/tests/tree.rs b/zebra-chain/src/orchard/tests/tree.rs new file mode 100644 index 00000000..d607ac4b --- /dev/null +++ b/zebra-chain/src/orchard/tests/tree.rs @@ -0,0 +1,47 @@ +use halo2::arithmetic::FieldExt; +use halo2::pasta::pallas; + +use crate::orchard::tests::test_vectors; +use crate::orchard::tree::*; + +#[test] +fn empty_roots() { + zebra_test::init(); + + for i in 0..EMPTY_ROOTS.len() { + assert_eq!( + EMPTY_ROOTS[i].to_bytes(), + // The test vector is in reversed order. + test_vectors::EMPTY_ROOTS[MERKLE_DEPTH - i] + ); + } +} + +#[test] +fn incremental_roots() { + zebra_test::init(); + + let mut leaves = vec![]; + + let mut incremental_tree = NoteCommitmentTree::default(); + + for (i, commitment_set) in test_vectors::COMMITMENTS.iter().enumerate() { + for cm_x_bytes in commitment_set.iter() { + let cm_x = pallas::Base::from_bytes(&cm_x_bytes).unwrap(); + + leaves.push(cm_x); + + let _ = incremental_tree.append(cm_x); + } + + assert_eq!( + hex::encode(incremental_tree.hash()), + hex::encode(test_vectors::ROOTS[i].anchor) + ); + + assert_eq!( + hex::encode((NoteCommitmentTree::from(leaves.clone())).hash()), + hex::encode(test_vectors::ROOTS[i].anchor) + ); + } +} diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs index 0bc2db85..2df18685 100644 --- a/zebra-chain/src/orchard/tree.rs +++ b/zebra-chain/src/orchard/tree.rs @@ -1,7 +1,7 @@ //! Note Commitment Trees. //! //! A note commitment tree is an incremental Merkle tree of fixed depth -//! used to store note commitments that JoinSplit transfers or Spend +//! used to store note commitments that Action //! transfers produce. Just as the unspent transaction output set (UTXO //! set) used in Bitcoin, it is used to express the existence of value and //! the capability to spend it. However, unlike the UTXO set, it is not @@ -15,7 +15,6 @@ #![allow(dead_code)] use std::{ - collections::VecDeque, convert::TryFrom, fmt, hash::{Hash, Hasher}, @@ -24,65 +23,65 @@ use std::{ use bitvec::prelude::*; use halo2::{arithmetic::FieldExt, pasta::pallas}; +use incrementalmerkletree::{bridgetree, Frontier}; use lazy_static::lazy_static; +use thiserror::Error; -use super::{commitment::NoteCommitment, sinsemilla::*}; +use super::sinsemilla::*; use crate::serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }; -const MERKLE_DEPTH: usize = 32; +pub(super) const MERKLE_DEPTH: usize = 32; /// MerkleCRH^Orchard Hash Function /// /// Used to hash incremental Merkle tree hash values for Orchard. /// -/// MerkleCRH^Orchard: {0..MerkleDepth^Orchard βˆ’ 1} Γ— Pπ‘₯ βˆͺ {βŠ₯} Γ— Pπ‘₯ βˆͺ {βŠ₯} β†’ Pπ‘₯ βˆͺ {βŠ₯} +/// MerkleCRH^Orchard: {0..MerkleDepth^Orchard βˆ’ 1} Γ— Pπ‘₯ Γ— Pπ‘₯ β†’ Pπ‘₯ /// -/// MerkleCRH^Orchard(layer, left, right) := SinsemillaHash("z.cash:Orchard-MerkleCRH", l || left || right), +/// MerkleCRH^Orchard(layer, left, right) := 0 if hash == βŠ₯; hash otherwise /// -/// where l = I2LEBSP_10(MerkleDepth^Orchard βˆ’ 1 βˆ’ layer), and left, right, and +/// where hash = SinsemillaHash("z.cash:Orchard-MerkleCRH", l || left || right), +/// l = I2LEBSP_10(MerkleDepth^Orchard βˆ’ 1 βˆ’ layer), and left, right, and /// the output are the x-coordinates of Pallas affine points. /// /// https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh /// https://zips.z.cash/protocol/protocol.pdf#constants -fn merkle_crh_orchard( - layer: u8, - maybe_left: Option, - maybe_right: Option, -) -> Option { - match (maybe_left, maybe_right) { - (None, _) | (_, None) => None, - (Some(left), Some(right)) => { - let mut s = bitvec![Lsb0, u8;]; +fn merkle_crh_orchard(layer: u8, left: pallas::Base, right: pallas::Base) -> pallas::Base { + let mut s = bitvec![Lsb0, u8;]; - // Prefix: l = I2LEBSP_10(MerkleDepth^Orchard βˆ’ 1 βˆ’ layer) - let l = MERKLE_DEPTH - 1 - layer as usize; - s.extend_from_bitslice(&BitArray::::from([l, 0])[0..10]); - s.extend_from_bitslice(&BitArray::::from(left.to_bytes())[0..255]); - s.extend_from_bitslice(&BitArray::::from(right.to_bytes())[0..255]); + // Prefix: l = I2LEBSP_10(MerkleDepth^Orchard βˆ’ 1 βˆ’ layer) + let l = MERKLE_DEPTH - 1 - layer as usize; + s.extend_from_bitslice(&BitArray::::from([l, 0])[0..10]); + s.extend_from_bitslice(&BitArray::::from(left.to_bytes())[0..255]); + s.extend_from_bitslice(&BitArray::::from(right.to_bytes())[0..255]); - sinsemilla_hash(b"z.cash:Orchard-MerkleCRH", &s) - } + match sinsemilla_hash(b"z.cash:Orchard-MerkleCRH", &s) { + Some(h) => h, + None => pallas::Base::zero(), } } lazy_static! { - /// Orchard note commitment trees have a max depth of 32. + /// List of "empty" Orchard note commitment nodes, one for each layer. /// - /// https://zips.z.cash/protocol/nu5.pdf#constants - static ref EMPTY_ROOTS: Vec = { - - // The empty leaf node, Uncommitted^Orchard = I2LEBSP_l_MerkleOrchard(2) + /// The list is indexed by the layer number (0: root; MERKLE_DEPTH: leaf). + /// + /// https://zips.z.cash/protocol/protocol.pdf#constants + pub(super) static ref EMPTY_ROOTS: Vec = { + // The empty leaf node. This is layer 32. let mut v = vec![NoteCommitmentTree::uncommitted()]; // Starting with layer 31 (the first internal layer, after the leaves), // generate the empty roots up to layer 0, the root. - for d in 0..MERKLE_DEPTH + for layer in (0..MERKLE_DEPTH).rev() { - let next = merkle_crh_orchard((MERKLE_DEPTH - 1 - d) as u8, Some(v[d]), Some(v[d])).unwrap(); - v.push(next); + // The vector is generated from the end, pushing new nodes to its beginning. + // For this reason, the layer below is v[0]. + let next = merkle_crh_orchard(layer as u8, v[0], v[0]); + v.insert(0, next); } v @@ -90,12 +89,6 @@ lazy_static! { }; } -/// The index of a note’s commitment at the leafmost layer of its -/// `NoteCommitmentTree`. -/// -/// https://zips.z.cash/protocol/nu5.pdf#merkletree -pub struct Position(pub(crate) u64); - /// Orchard note commitment tree root node hash. /// /// The root hash in LEBS2OSP256(rt) encoding of the Orchard note commitment @@ -154,70 +147,81 @@ impl ZcashDeserialize for Root { } } -/// Orchard Note Commitment Tree -#[derive(Clone, Debug, Default, Eq, PartialEq)] -struct NoteCommitmentTree { - /// The root node of the tree (often used as an anchor). - root: Root, - /// The height of the tree (maximum height for Orchard is 32). - height: u8, - /// The number of leaves (note commitments) in this tree. - count: u32, -} +/// A node of the Orchard Incremental Note Commitment Tree. +#[derive(Clone, Debug)] +struct Node(pallas::Base); -impl From> for NoteCommitmentTree { - fn from(_values: Vec) -> Self { - unimplemented!(); +impl incrementalmerkletree::Hashable for Node { + fn empty_leaf() -> Self { + Self(NoteCommitmentTree::uncommitted()) + } + + /// Combine two nodes to generate a new node in the given level. + /// Level 0 is the layer above the leaves (layer 31). + /// Level 31 is the root (layer 0). + fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self { + let layer = (MERKLE_DEPTH - 1) as u8 - u8::from(level); + Self(merkle_crh_orchard(layer, a.0, b.0)) + } + + /// Return the node for the level below the given level. (A quirk of the API) + fn empty_root(level: incrementalmerkletree::Altitude) -> Self { + let layer_below: usize = MERKLE_DEPTH - usize::from(level); + Self(EMPTY_ROOTS[layer_below]) } } -impl From> for NoteCommitmentTree { - fn from(values: Vec) -> Self { - if values.is_empty() { - return NoteCommitmentTree { - root: Root::default(), - height: 0, - count: 0, - }; - } - - let count = values.len() as u32; - let mut height = 0u8; - let mut current_layer: VecDeque = values.into_iter().collect(); - - while usize::from(height) < MERKLE_DEPTH { - let mut next_layer_up = vec![]; - - while !current_layer.is_empty() { - let left = current_layer.pop_front().unwrap(); - let right; - if current_layer.is_empty() { - right = EMPTY_ROOTS[height as usize]; - } else { - right = current_layer.pop_front().unwrap(); - } - next_layer_up.push(merkle_crh_orchard(height, Some(left), Some(right)).unwrap()); - } - - height += 1; - current_layer = next_layer_up.into(); - } - - assert!(current_layer.len() == 1); - - NoteCommitmentTree { - root: Root(current_layer.pop_front().unwrap()), - height, - count, - } +impl From for Node { + fn from(x: pallas::Base) -> Self { + Node(x) } } +#[allow(dead_code, missing_docs)] +#[derive(Error, Debug, PartialEq)] +pub enum NoteCommitmentTreeError { + #[error("The note commitment tree is full")] + FullTree, +} + +/// Orchard Incremental Note Commitment Tree +#[derive(Clone, Debug)] +pub struct NoteCommitmentTree { + /// The tree represented as a Frontier. + /// + /// A Frontier is a subset of the tree that allows to fully specify it. + /// It consists of nodes along the rightmost (newer) branch of the tree that + /// has non-empty nodes. Upper (near root) empty nodes of the branch are not + /// stored. + inner: bridgetree::Frontier, +} + impl NoteCommitmentTree { - /// Get the Pallas-based Sinsemilla hash of root node of this merkle tree of + /// Adds a note commitment x-coordinate to the tree. + /// + /// The leaves of the tree are actually a base field element, the + /// x-coordinate of the commitment, the data that is actually stored on the + /// chain and input into the proof. + /// + /// Returns an error if the tree is full. + pub fn append(&mut self, cm_x: pallas::Base) -> Result<(), NoteCommitmentTreeError> { + if self.inner.append(&cm_x.into()) { + Ok(()) + } else { + Err(NoteCommitmentTreeError::FullTree) + } + } + + /// Returns the current root of the tree, used as an anchor in Orchard + /// shielded transactions. + pub fn root(&self) -> Root { + Root(self.inner.root().0) + } + + /// Get the Pallas-based Sinsemilla hash / root node of this merkle tree of /// note commitments. pub fn hash(&self) -> [u8; 32] { - self.root.into() + self.root().into() } /// An as-yet unused Orchard note commitment tree leaf node. @@ -228,22 +232,46 @@ impl NoteCommitmentTree { pub fn uncommitted() -> pallas::Base { pallas::Base::one().double() } + + /// Count of note commitments added to the tree. + /// + /// For Orchard, the tree is capped at 2^32. + pub fn count(&self) -> u64 { + self.inner + .position() + .map_or(0, |pos| usize::from(pos) as u64 + 1) + } } -// TODO: check empty roots, incremental roots, as part of https://github.com/ZcashFoundation/zebra/issues/1287 - -#[cfg(test)] -mod tests { - - use super::*; - use crate::orchard::tests::test_vectors; - - #[test] - fn empty_roots() { - zebra_test::init(); - - for i in 0..EMPTY_ROOTS.len() { - assert_eq!(EMPTY_ROOTS[i].to_bytes(), test_vectors::EMPTY_ROOTS[i]); +impl Default for NoteCommitmentTree { + fn default() -> Self { + Self { + inner: bridgetree::Frontier::new(), } } } + +impl Eq for NoteCommitmentTree {} + +impl PartialEq for NoteCommitmentTree { + fn eq(&self, other: &Self) -> bool { + self.hash() == other.hash() + } +} + +impl From> for NoteCommitmentTree { + /// Compute the tree from a whole bunch of note commitments at once. + fn from(values: Vec) -> Self { + let mut tree = Self::default(); + + if values.is_empty() { + return tree; + } + + for cm_x in values { + let _ = tree.append(cm_x); + } + + tree + } +} diff --git a/zebra-chain/src/sapling/tests.rs b/zebra-chain/src/sapling/tests.rs index a7ac2ac3..2b8339b2 100644 --- a/zebra-chain/src/sapling/tests.rs +++ b/zebra-chain/src/sapling/tests.rs @@ -1,2 +1,4 @@ mod preallocate; mod prop; +mod test_vectors; +mod tree; diff --git a/zebra-chain/src/sapling/tests/test_vectors.rs b/zebra-chain/src/sapling/tests/test_vectors.rs new file mode 100644 index 00000000..b726cc56 --- /dev/null +++ b/zebra-chain/src/sapling/tests/test_vectors.rs @@ -0,0 +1,79 @@ +// From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L512 +pub const HEX_EMPTY_ROOTS: [&str; 33] = [ + "0100000000000000000000000000000000000000000000000000000000000000", + "817de36ab2d57feb077634bca77819c8e0bd298c04f6fed0e6a83cc1356ca155", + "ffe9fc03f18b176c998806439ff0bb8ad193afdb27b2ccbc88856916dd804e34", + "d8283386ef2ef07ebdbb4383c12a739a953a4d6e0d6fb1139a4036d693bfbb6c", + "e110de65c907b9dea4ae0bd83a4b0a51bea175646a64c12b4c9f931b2cb31b49", + "912d82b2c2bca231f71efcf61737fbf0a08befa0416215aeef53e8bb6d23390a", + "8ac9cf9c391e3fd42891d27238a81a8a5c1d3a72b1bcbea8cf44a58ce7389613", + "d6c639ac24b46bd19341c91b13fdcab31581ddaf7f1411336a271f3d0aa52813", + "7b99abdc3730991cc9274727d7d82d28cb794edbc7034b4f0053ff7c4b680444", + "43ff5457f13b926b61df552d4e402ee6dc1463f99a535f9a713439264d5b616b", + "ba49b659fbd0b7334211ea6a9d9df185c757e70aa81da562fb912b84f49bce72", + "4777c8776a3b1e69b73a62fa701fa4f7a6282d9aee2c7a6b82e7937d7081c23c", + "ec677114c27206f5debc1c1ed66f95e2b1885da5b7be3d736b1de98579473048", + "1b77dac4d24fb7258c3c528704c59430b630718bec486421837021cf75dab651", + "bd74b25aacb92378a871bf27d225cfc26baca344a1ea35fdd94510f3d157082c", + "d6acdedf95f608e09fa53fb43dcd0990475726c5131210c9e5caeab97f0e642f", + "1ea6675f9551eeb9dfaaa9247bc9858270d3d3a4c5afa7177a984d5ed1be2451", + "6edb16d01907b759977d7650dad7e3ec049af1a3d875380b697c862c9ec5d51c", + "cd1c8dbf6e3acc7a80439bc4962cf25b9dce7c896f3a5bd70803fc5a0e33cf00", + "6aca8448d8263e547d5ff2950e2ed3839e998d31cbc6ac9fd57bc6002b159216", + "8d5fa43e5a10d11605ac7430ba1f5d81fb1b68d29a640405767749e841527673", + "08eeab0c13abd6069e6310197bf80f9c1ea6de78fd19cbae24d4a520e6cf3023", + "0769557bc682b1bf308646fd0b22e648e8b9e98f57e29f5af40f6edb833e2c49", + "4c6937d78f42685f84b43ad3b7b00f81285662f85c6a68ef11d62ad1a3ee0850", + "fee0e52802cb0c46b1eb4d376c62697f4759f6c8917fa352571202fd778fd712", + "16d6252968971a83da8521d65382e61f0176646d771c91528e3276ee45383e4a", + "d2e1642c9a462229289e5b0e3b7f9008e0301cbb93385ee0e21da2545073cb58", + "a5122c08ff9c161d9ca6fc462073396c7d7d38e8ee48cdb3bea7e2230134ed6a", + "28e7b841dcbc47cceb69d7cb8d94245fb7cb2ba3a7a6bc18f13f945f7dbd6e2a", + "e1f34b034d4a3cd28557e2907ebf990c918f64ecb50a94f01d6fda5ca5c7ef72", + "12935f14b676509b81eb49ef25f39269ed72309238b4c145803544b646dca62d", + "b2eed031d4d6a4f02a097f80b54cc1541d4163c6b6f5971f88b6e41d35c53814", + "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e", +]; + +// From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments_sapling.json +// Byte-reversed from those ones because the original test vectors are loaded using uint256S() +pub const COMMITMENTS: [&str; 16] = [ + "b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55", + "225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b11458", + "7c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c", + "50421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a030", + "aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc12", + "f76748d40d5ee5f9a608512e7954dd515f86e8f6d009141c89163de1cf351a02", + "bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0b2e", + "da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a511", + "3a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f77446", + "c7ca8f7df8fd997931d33985d935ee2d696856cc09cc516d419ea6365f163008", + "f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c3702", + "e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c608", + "8cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826", + "22fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03", + "f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c", + "3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15", +]; + +// Calculated by modifying TestCommitmentTree in +// https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs +// to compute the full Sapling height root (32). +pub const ROOTS: [&str; 16] = [ + "ee880ed73e96ba0739578c87ba8e6a4bc33b5e63bb98875e6e2f04b214e9fb59", + "321aef631f1a9b7914d40d7bab34c29145ac6cf69d24bf0fc566b33ac9029972", + "ddaa1ab86de5c153993414f34ba97e9674c459dfadde112b89eeeafa0e5a204c", + "0b337c75535b09468955d499e37cb7e2466f1f0c861ddea929aa13c699c1a454", + "5a9b9764d76a45848012eec306d6f6bface319ad5d9bf88db96b3b19edded716", + "004075c72e360d7b2ab113555e97dcf4fb50f211d74841eafb05aaff705e3235", + "ebf2139c2ef10d51f21fee18521963b91b64987f2743d908be2b80b4ae29e622", + "70d07f5662eafaf054327899abce515b1c1cbac6600edea86297c2800e806534", + "f72dad9cd0f4d4783444f6dc64d9be2edc74cffddcb60bf244e56eada508c22a", + "7635d357c7755c91ea4d6b53e8fd42756329118577fe8b9ade3d33b316fa4948", + "fca0c26ce07fc7e563b031d9187f829fa41715f193f08bd0ac25e5122ac75c2e", + "0b727c9c6f66c3c749ef9c1df6c5356db8adf80fcc3c1d7fdf56b82cb8d47a3c", + "d77d030ed3c2521567eae9555b95eca89442b0c263b82fea4359f802e0f31668", + "3d84c8b65e5a8036d115161bb6e3ca2a556e42d376abc3d74a16bc22685b7d61", + "84f752458538a24483e9731e32fa95cabf56aebbbc6bff8475f45299bcdcba35", + "bb3cc8f85773c05f3332a25cc8281a68450a90807cef859b49b2f1d9d2d3a64d", +]; diff --git a/zebra-chain/src/sapling/tests/tree.rs b/zebra-chain/src/sapling/tests/tree.rs new file mode 100644 index 00000000..b7708f4a --- /dev/null +++ b/zebra-chain/src/sapling/tests/tree.rs @@ -0,0 +1,43 @@ +use hex::FromHex; + +use crate::sapling::tests::test_vectors; +use crate::sapling::tree::*; + +#[test] +fn empty_roots() { + zebra_test::init(); + + for i in 0..EMPTY_ROOTS.len() { + assert_eq!( + hex::encode(EMPTY_ROOTS[i]), + // The test vector is in reversed order. + test_vectors::HEX_EMPTY_ROOTS[MERKLE_DEPTH - i] + ); + } +} + +#[test] +fn incremental_roots() { + zebra_test::init(); + + let mut leaves = vec![]; + + let mut incremental_tree = NoteCommitmentTree::default(); + + for (i, cm_u) in test_vectors::COMMITMENTS.iter().enumerate() { + let bytes = <[u8; 32]>::from_hex(cm_u).unwrap(); + + let cm_u = jubjub::Fq::from_bytes(&bytes).unwrap(); + + leaves.push(cm_u); + + let _ = incremental_tree.append(cm_u); + + assert_eq!(hex::encode(incremental_tree.hash()), test_vectors::ROOTS[i]); + + assert_eq!( + hex::encode((NoteCommitmentTree::from(leaves.clone())).hash()), + test_vectors::ROOTS[i] + ); + } +} diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index bca513d9..bd029c6a 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -13,13 +13,16 @@ #![allow(clippy::unit_arg)] #![allow(dead_code)] -use std::{collections::VecDeque, fmt}; +use std::fmt; -use super::commitment::{pedersen_hashes::pedersen_hash, NoteCommitment}; use bitvec::prelude::*; +use incrementalmerkletree::{bridgetree, Frontier}; use lazy_static::lazy_static; +use thiserror::Error; -const MERKLE_DEPTH: usize = 32; +use super::commitment::pedersen_hashes::pedersen_hash; + +pub(super) const MERKLE_DEPTH: usize = 32; /// MerkleCRH^Sapling Hash Function /// @@ -34,7 +37,8 @@ fn merkle_crh_sapling(layer: u8, left: [u8; 32], right: [u8; 32]) -> [u8; 32] { let mut s = bitvec![Lsb0, u8;]; // Prefix: l = I2LEBSP_6(MerkleDepth^Sapling βˆ’ 1 βˆ’ layer) - s.extend_from_bitslice(&BitSlice::::from_element(&layer)[0..6]); + let l = (MERKLE_DEPTH - 1) as u8 - layer; + s.extend_from_bitslice(&BitSlice::::from_element(&l)[0..6]); s.extend_from_bitslice(&BitArray::::from(left)[0..255]); s.extend_from_bitslice(&BitArray::::from(right)[0..255]); @@ -42,16 +46,22 @@ fn merkle_crh_sapling(layer: u8, left: [u8; 32], right: [u8; 32]) -> [u8; 32] { } lazy_static! { - /// Sapling note commitment trees have a max depth of 32. + /// List of "empty" Sapling note commitment nodes, one for each layer. + /// + /// The list is indexed by the layer number (0: root; MERKLE_DEPTH: leaf). /// /// https://zips.z.cash/protocol/protocol.pdf#constants - static ref EMPTY_ROOTS: Vec<[u8; 32]> = { - // Uncommitted^Sapling = I2LEBSP_l_MerkleSapling(1) - let mut v = vec![jubjub::Fq::one().to_bytes()]; + pub(super) static ref EMPTY_ROOTS: Vec<[u8; 32]> = { + // The empty leaf node. This is layer 32. + let mut v = vec![NoteCommitmentTree::uncommitted()]; - for d in 0..MERKLE_DEPTH { - let next = merkle_crh_sapling(d as u8, v[d], v[d]); - v.push(next); + // Starting with layer 31 (the first internal layer, after the leaves), + // generate the empty roots up to layer 0, the root. + for layer in (0..MERKLE_DEPTH).rev() { + // The vector is generated from the end, pushing new nodes to its beginning. + // For this reason, the layer below is v[0]. + let next = merkle_crh_sapling(layer as u8, v[0], v[0]); + v.insert(0, next); } v @@ -104,183 +114,134 @@ impl From<&Root> for [u8; 32] { } } -/// Sapling Note Commitment Tree -#[derive(Clone, Debug, Default, Eq, PartialEq)] -struct NoteCommitmentTree { - /// The root node of the tree (often used as an anchor). - root: Root, - /// The height of the tree (maximum height for Sapling is 32). - height: u8, - /// The number of leaves (note commitments) in this tree. - count: u32, +/// A node of the Sapling Incremental Note Commitment Tree. +/// +/// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq) +/// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs. +#[derive(Clone, Debug)] +struct Node([u8; 32]); + +impl incrementalmerkletree::Hashable for Node { + fn empty_leaf() -> Self { + Self(NoteCommitmentTree::uncommitted()) + } + + /// Combine two nodes to generate a new node in the given level. + /// Level 0 is the layer above the leaves (layer 31). + /// Level 31 is the root (layer 0). + fn combine(level: incrementalmerkletree::Altitude, a: &Self, b: &Self) -> Self { + let layer = (MERKLE_DEPTH - 1) as u8 - u8::from(level); + Self(merkle_crh_sapling(layer, a.0, b.0)) + } + + /// Return the node for the level below the given level. (A quirk of the API) + fn empty_root(level: incrementalmerkletree::Altitude) -> Self { + let layer_below: usize = MERKLE_DEPTH - usize::from(level); + Self(EMPTY_ROOTS[layer_below]) + } } -impl From> for NoteCommitmentTree { - fn from(_values: Vec) -> Self { - unimplemented!(); +impl From for Node { + fn from(x: jubjub::Fq) -> Self { + Node(x.into()) + } +} + +#[allow(dead_code, missing_docs)] +#[derive(Error, Debug, PartialEq)] +pub enum NoteCommitmentTreeError { + #[error("The note commitment tree is full")] + FullTree, +} + +/// Sapling Incremental Note Commitment Tree. +#[derive(Clone, Debug)] +pub struct NoteCommitmentTree { + /// The tree represented as a Frontier. + /// + /// A Frontier is a subset of the tree that allows to fully specify it. + /// It consists of nodes along the rightmost (newer) branch of the tree that + /// has non-empty nodes. Upper (near root) empty nodes of the branch are not + /// stored. + inner: bridgetree::Frontier, +} + +impl NoteCommitmentTree { + /// Adds a note commitment u-coordinate to the tree. + /// + /// The leaves of the tree are actually a base field element, the + /// u-coordinate of the commitment, the data that is actually stored on the + /// chain and input into the proof. + /// + /// Returns an error if the tree is full. + pub fn append(&mut self, cm_u: jubjub::Fq) -> Result<(), NoteCommitmentTreeError> { + if self.inner.append(&cm_u.into()) { + Ok(()) + } else { + Err(NoteCommitmentTreeError::FullTree) + } + } + + /// Returns the current root of the tree, used as an anchor in Sapling + /// shielded transactions. + pub fn root(&self) -> Root { + Root(self.inner.root().0) + } + + /// Get the Jubjub-based Pedersen hash of root node of this merkle tree of + /// note commitments. + pub fn hash(&self) -> [u8; 32] { + self.root().into() + } + + /// An as-yet unused Sapling note commitment tree leaf node. + /// + /// Distinct for Sapling, a distinguished hash value of: + /// + /// Uncommitted^Sapling = I2LEBSP_l_MerkleSapling(1) + pub fn uncommitted() -> [u8; 32] { + jubjub::Fq::one().to_bytes() + } + + /// Count of note commitments added to the tree. + /// + /// For Sapling, the tree is capped at 2^32. + pub fn count(&self) -> u64 { + self.inner + .position() + .map_or(0, |pos| usize::from(pos) as u64 + 1) + } +} + +impl Default for NoteCommitmentTree { + fn default() -> Self { + Self { + inner: bridgetree::Frontier::new(), + } + } +} + +impl Eq for NoteCommitmentTree {} + +impl PartialEq for NoteCommitmentTree { + fn eq(&self, other: &Self) -> bool { + self.hash() == other.hash() } } impl From> for NoteCommitmentTree { + /// Compute the tree from a whole bunch of note commitments at once. fn from(values: Vec) -> Self { + let mut tree = Self::default(); + if values.is_empty() { - return NoteCommitmentTree { - root: Root::default(), - height: 0, - count: 0, - }; + return tree; } - let count = values.len() as u32; - let mut height = 0u8; - let mut current_layer: VecDeque<[u8; 32]> = - values.into_iter().map(|cm_u| cm_u.to_bytes()).collect(); - - while usize::from(height) < MERKLE_DEPTH { - let mut next_layer_up = vec![]; - - while !current_layer.is_empty() { - let left = current_layer.pop_front().unwrap(); - let right; - if current_layer.is_empty() { - right = EMPTY_ROOTS[height as usize]; - } else { - right = current_layer.pop_front().unwrap(); - } - next_layer_up.push(merkle_crh_sapling(height, left, right)); - } - - height += 1; - current_layer = next_layer_up.into(); + for cm_u in values { + let _ = tree.append(cm_u); } - assert!(current_layer.len() == 1); - - NoteCommitmentTree { - root: Root(current_layer.pop_front().unwrap()), - height, - count, - } - } -} - -impl NoteCommitmentTree { - /// Get the Jubjub-based Pedersen hash of root node of this merkle tree of - /// commitment notes. - pub fn hash(&self) -> [u8; 32] { - self.root.0 - } -} - -#[cfg(test)] -mod tests { - - use hex::FromHex; - - use super::*; - - #[test] - fn empty_roots() { - zebra_test::init(); - - // From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L512 - const HEX_EMPTY_ROOTS: [&str; 33] = [ - "0100000000000000000000000000000000000000000000000000000000000000", - "817de36ab2d57feb077634bca77819c8e0bd298c04f6fed0e6a83cc1356ca155", - "ffe9fc03f18b176c998806439ff0bb8ad193afdb27b2ccbc88856916dd804e34", - "d8283386ef2ef07ebdbb4383c12a739a953a4d6e0d6fb1139a4036d693bfbb6c", - "e110de65c907b9dea4ae0bd83a4b0a51bea175646a64c12b4c9f931b2cb31b49", - "912d82b2c2bca231f71efcf61737fbf0a08befa0416215aeef53e8bb6d23390a", - "8ac9cf9c391e3fd42891d27238a81a8a5c1d3a72b1bcbea8cf44a58ce7389613", - "d6c639ac24b46bd19341c91b13fdcab31581ddaf7f1411336a271f3d0aa52813", - "7b99abdc3730991cc9274727d7d82d28cb794edbc7034b4f0053ff7c4b680444", - "43ff5457f13b926b61df552d4e402ee6dc1463f99a535f9a713439264d5b616b", - "ba49b659fbd0b7334211ea6a9d9df185c757e70aa81da562fb912b84f49bce72", - "4777c8776a3b1e69b73a62fa701fa4f7a6282d9aee2c7a6b82e7937d7081c23c", - "ec677114c27206f5debc1c1ed66f95e2b1885da5b7be3d736b1de98579473048", - "1b77dac4d24fb7258c3c528704c59430b630718bec486421837021cf75dab651", - "bd74b25aacb92378a871bf27d225cfc26baca344a1ea35fdd94510f3d157082c", - "d6acdedf95f608e09fa53fb43dcd0990475726c5131210c9e5caeab97f0e642f", - "1ea6675f9551eeb9dfaaa9247bc9858270d3d3a4c5afa7177a984d5ed1be2451", - "6edb16d01907b759977d7650dad7e3ec049af1a3d875380b697c862c9ec5d51c", - "cd1c8dbf6e3acc7a80439bc4962cf25b9dce7c896f3a5bd70803fc5a0e33cf00", - "6aca8448d8263e547d5ff2950e2ed3839e998d31cbc6ac9fd57bc6002b159216", - "8d5fa43e5a10d11605ac7430ba1f5d81fb1b68d29a640405767749e841527673", - "08eeab0c13abd6069e6310197bf80f9c1ea6de78fd19cbae24d4a520e6cf3023", - "0769557bc682b1bf308646fd0b22e648e8b9e98f57e29f5af40f6edb833e2c49", - "4c6937d78f42685f84b43ad3b7b00f81285662f85c6a68ef11d62ad1a3ee0850", - "fee0e52802cb0c46b1eb4d376c62697f4759f6c8917fa352571202fd778fd712", - "16d6252968971a83da8521d65382e61f0176646d771c91528e3276ee45383e4a", - "d2e1642c9a462229289e5b0e3b7f9008e0301cbb93385ee0e21da2545073cb58", - "a5122c08ff9c161d9ca6fc462073396c7d7d38e8ee48cdb3bea7e2230134ed6a", - "28e7b841dcbc47cceb69d7cb8d94245fb7cb2ba3a7a6bc18f13f945f7dbd6e2a", - "e1f34b034d4a3cd28557e2907ebf990c918f64ecb50a94f01d6fda5ca5c7ef72", - "12935f14b676509b81eb49ef25f39269ed72309238b4c145803544b646dca62d", - "b2eed031d4d6a4f02a097f80b54cc1541d4163c6b6f5971f88b6e41d35c53814", - "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e", - ]; - - for i in 0..EMPTY_ROOTS.len() { - assert_eq!(hex::encode(EMPTY_ROOTS[i]), HEX_EMPTY_ROOTS[i]); - } - } - - #[test] - fn incremental_roots() { - zebra_test::init(); - // From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments_sapling.json - // Byte-reversed from those ones because the original test vectors are loaded using uint256S() - let commitments = [ - "b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55", - "225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b11458", - "7c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c", - "50421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a030", - "aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc12", - "f76748d40d5ee5f9a608512e7954dd515f86e8f6d009141c89163de1cf351a02", - "bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0b2e", - "da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a511", - "3a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f77446", - "c7ca8f7df8fd997931d33985d935ee2d696856cc09cc516d419ea6365f163008", - "f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c3702", - "e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c608", - "8cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826", - "22fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03", - "f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c", - "3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15", - ]; - - // Calculated by modifying TestCommitmentTree in - // https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs - // to compute the full Sapling height root (32). - let roots = [ - "ee880ed73e96ba0739578c87ba8e6a4bc33b5e63bb98875e6e2f04b214e9fb59", - "321aef631f1a9b7914d40d7bab34c29145ac6cf69d24bf0fc566b33ac9029972", - "ddaa1ab86de5c153993414f34ba97e9674c459dfadde112b89eeeafa0e5a204c", - "0b337c75535b09468955d499e37cb7e2466f1f0c861ddea929aa13c699c1a454", - "5a9b9764d76a45848012eec306d6f6bface319ad5d9bf88db96b3b19edded716", - "004075c72e360d7b2ab113555e97dcf4fb50f211d74841eafb05aaff705e3235", - "ebf2139c2ef10d51f21fee18521963b91b64987f2743d908be2b80b4ae29e622", - "70d07f5662eafaf054327899abce515b1c1cbac6600edea86297c2800e806534", - "f72dad9cd0f4d4783444f6dc64d9be2edc74cffddcb60bf244e56eada508c22a", - "7635d357c7755c91ea4d6b53e8fd42756329118577fe8b9ade3d33b316fa4948", - "fca0c26ce07fc7e563b031d9187f829fa41715f193f08bd0ac25e5122ac75c2e", - "0b727c9c6f66c3c749ef9c1df6c5356db8adf80fcc3c1d7fdf56b82cb8d47a3c", - "d77d030ed3c2521567eae9555b95eca89442b0c263b82fea4359f802e0f31668", - "3d84c8b65e5a8036d115161bb6e3ca2a556e42d376abc3d74a16bc22685b7d61", - "84f752458538a24483e9731e32fa95cabf56aebbbc6bff8475f45299bcdcba35", - "bb3cc8f85773c05f3332a25cc8281a68450a90807cef859b49b2f1d9d2d3a64d", - ]; - - let mut leaves = vec![]; - - for (i, cm_u) in commitments.iter().enumerate() { - let bytes = <[u8; 32]>::from_hex(cm_u).unwrap(); - - leaves.push(jubjub::Fq::from_bytes(&bytes).unwrap()); - - let tree = NoteCommitmentTree::from(leaves.clone()); - - assert_eq!(hex::encode(tree.hash()), roots[i]); - } + tree } }